From 44e882d7fcc32f5edc173a127dbd23cd8a639266 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 4 Jul 2020 21:25:43 -0500 Subject: [PATCH] Show donor badge --- app/soapbox/actions/patron.js | 41 ++++++++++++++++--- app/soapbox/components/sidebar_menu.js | 2 +- .../features/account_timeline/index.js | 17 +++++++- .../features/ui/components/funding_panel.js | 3 +- app/soapbox/pages/profile_page.js | 4 +- app/soapbox/reducers/__tests__/patron-test.js | 23 ++++++++++- app/soapbox/reducers/patron.js | 14 ++++++- app/soapbox/selectors/index.js | 13 +++++- 8 files changed, 103 insertions(+), 14 deletions(-) diff --git a/app/soapbox/actions/patron.js b/app/soapbox/actions/patron.js index b9e55331d..8cf64d4c4 100644 --- a/app/soapbox/actions/patron.js +++ b/app/soapbox/actions/patron.js @@ -2,30 +2,61 @@ import api from '../api'; export const PATRON_INSTANCE_FETCH_REQUEST = 'PATRON_INSTANCE_FETCH_REQUEST'; export const PATRON_INSTANCE_FETCH_SUCCESS = 'PATRON_INSTANCE_FETCH_SUCCESS'; -export const PATRON_INSTANCE_FETCH_FAIL = 'PATRON_INSTANCE_FETCH_FAIL'; +export const PATRON_INSTANCE_FETCH_FAIL = 'PATRON_INSTANCE_FETCH_FAIL'; + +export const PATRON_ACCOUNT_FETCH_REQUEST = 'PATRON_ACCOUNT_FETCH_REQUEST'; +export const PATRON_ACCOUNT_FETCH_SUCCESS = 'PATRON_ACCOUNT_FETCH_SUCCESS'; +export const PATRON_ACCOUNT_FETCH_FAIL = 'PATRON_ACCOUNT_FETCH_FAIL'; export function fetchPatronInstance() { return (dispatch, getState) => { dispatch({ type: PATRON_INSTANCE_FETCH_REQUEST }); api(getState).get('/api/patron/v1/instance').then(response => { - dispatch(importFetchedFunding(response.data)); + dispatch(importFetchedInstance(response.data)); }).catch(error => { - dispatch(fetchFundingFail(error)); + dispatch(fetchInstanceFail(error)); }); }; }; -export function importFetchedFunding(instance) { +export function fetchPatronAccount(apId) { + return (dispatch, getState) => { + apId = encodeURIComponent(apId); + dispatch({ type: PATRON_ACCOUNT_FETCH_REQUEST }); + api(getState).get(`/api/patron/v1/accounts/${apId}`).then(response => { + dispatch(importFetchedAccount(response.data)); + }).catch(error => { + dispatch(fetchAccountFail(error)); + }); + }; +} + +function importFetchedInstance(instance) { return { type: PATRON_INSTANCE_FETCH_SUCCESS, instance, }; } -export function fetchFundingFail(error) { +function fetchInstanceFail(error) { return { type: PATRON_INSTANCE_FETCH_FAIL, error, skipAlert: true, }; }; + +function importFetchedAccount(account) { + return { + type: PATRON_ACCOUNT_FETCH_SUCCESS, + account, + }; +} + +function fetchAccountFail(error) { + return { + type: PATRON_ACCOUNT_FETCH_FAIL, + error, + skipAlert: true, + }; +} diff --git a/app/soapbox/components/sidebar_menu.js b/app/soapbox/components/sidebar_menu.js index 64b2d65b1..187f73135 100644 --- a/app/soapbox/components/sidebar_menu.js +++ b/app/soapbox/components/sidebar_menu.js @@ -43,7 +43,7 @@ const mapStateToProps = state => { return { account: getAccount(state, me), sidebarOpen: state.get('sidebar').sidebarOpen, - donateUrl: state.getIn(['patron', 'url']), + donateUrl: state.getIn(['patron', 'instance', 'url']), isStaff: isStaff(state.getIn(['accounts', me])), }; }; diff --git a/app/soapbox/features/account_timeline/index.js b/app/soapbox/features/account_timeline/index.js index f3d105b47..1ce040a3e 100644 --- a/app/soapbox/features/account_timeline/index.js +++ b/app/soapbox/features/account_timeline/index.js @@ -13,6 +13,7 @@ import { FormattedMessage } from 'react-intl'; import { fetchAccountIdentityProofs } from '../../actions/identity_proofs'; import MissingIndicator from 'soapbox/components/missing_indicator'; import { NavLink } from 'react-router-dom'; +import { fetchPatronAccount } from '../../actions/patron'; const emptyList = ImmutableList(); @@ -23,12 +24,14 @@ const mapStateToProps = (state, { params: { username }, withReplies = false }) = let accountId = -1; let accountUsername = username; + let accountApId = null; if (accountFetchError) { accountId = null; } else { let account = accounts.find(acct => username.toLowerCase() === acct.getIn(['acct'], '').toLowerCase()); accountId = account ? account.getIn(['id'], null) : -1; accountUsername = account ? account.getIn(['acct'], '') : ''; + accountApId = account ? account.get('url') : ''; } const path = withReplies ? `${accountId}:with_replies` : accountId; @@ -40,12 +43,14 @@ const mapStateToProps = (state, { params: { username }, withReplies = false }) = accountId, unavailable, accountUsername, + accountApId, isAccount: !!state.getIn(['accounts', accountId]), statusIds: state.getIn(['timelines', `account:${path}`, 'items'], emptyList), featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], emptyList), isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']), hasMore: state.getIn(['timelines', `account:${path}`, 'hasMore']), me, + patronEnabled: state.getIn(['soapbox', 'extensions', 'patron', 'enabled']), }; }; @@ -65,7 +70,7 @@ class AccountTimeline extends ImmutablePureComponent { }; componentDidMount() { - const { params: { username }, accountId, withReplies, me } = this.props; + const { params: { username }, accountId, accountApId, withReplies, me, patronEnabled } = this.props; if (accountId && accountId !== -1) { this.props.dispatch(fetchAccount(accountId)); @@ -75,6 +80,10 @@ class AccountTimeline extends ImmutablePureComponent { this.props.dispatch(expandAccountFeaturedTimeline(accountId)); } + if (patronEnabled && accountApId) { + this.props.dispatch(fetchPatronAccount(accountApId)); + } + this.props.dispatch(expandAccountTimeline(accountId, { withReplies })); } else { this.props.dispatch(fetchAccountByUsername(username)); @@ -82,7 +91,7 @@ class AccountTimeline extends ImmutablePureComponent { } componentDidUpdate(prevProps) { - const { me, accountId, withReplies } = this.props; + const { me, accountId, withReplies, accountApId, patronEnabled } = this.props; if (accountId && accountId !== -1 && (accountId !== prevProps.accountId && accountId) || withReplies !== prevProps.withReplies) { this.props.dispatch(fetchAccount(accountId)); if (me) this.props.dispatch(fetchAccountIdentityProofs(accountId)); @@ -91,6 +100,10 @@ class AccountTimeline extends ImmutablePureComponent { this.props.dispatch(expandAccountFeaturedTimeline(accountId)); } + if (patronEnabled && accountApId) { + this.props.dispatch(fetchPatronAccount(accountApId)); + } + this.props.dispatch(expandAccountTimeline(accountId, { withReplies })); } } diff --git a/app/soapbox/features/ui/components/funding_panel.js b/app/soapbox/features/ui/components/funding_panel.js index 5d58f1721..6d4084784 100644 --- a/app/soapbox/features/ui/components/funding_panel.js +++ b/app/soapbox/features/ui/components/funding_panel.js @@ -4,6 +4,7 @@ import { injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import ProgressBar from '../../../components/progress_bar'; import { fetchPatronInstance } from 'soapbox/actions/patron'; +import { Map as ImmutableMap } from 'immutable'; const moneyFormat = amount => ( new Intl @@ -63,7 +64,7 @@ class FundingPanel extends ImmutablePureComponent { const mapStateToProps = state => { return { - patron: state.get('patron'), + patron: state.getIn(['patron', 'instance'], ImmutableMap()), }; }; diff --git a/app/soapbox/pages/profile_page.js b/app/soapbox/pages/profile_page.js index 5f23ebf7b..fad1ced8b 100644 --- a/app/soapbox/pages/profile_page.js +++ b/app/soapbox/pages/profile_page.js @@ -11,10 +11,12 @@ import SignUpPanel from '../features/ui/components/sign_up_panel'; import ProfileInfoPanel from '../features/ui/components/profile_info_panel'; import { acctFull } from 'soapbox/utils/accounts'; import { getFeatures } from 'soapbox/utils/features'; +import { makeGetAccount } from '../selectors'; const mapStateToProps = (state, { params: { username }, withReplies = false }) => { const accounts = state.getIn(['accounts']); const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() === username.toLowerCase()); + const getAccount = makeGetAccount(); let accountId = -1; let account = null; @@ -30,7 +32,7 @@ const mapStateToProps = (state, { params: { username }, withReplies = false }) = //Children components fetch information return { - account, + account: accountId ? getAccount(state, accountId) : account, accountId, accountUsername, features: getFeatures(state.get('instance')), diff --git a/app/soapbox/reducers/__tests__/patron-test.js b/app/soapbox/reducers/__tests__/patron-test.js index 1cb8c728d..8a6001913 100644 --- a/app/soapbox/reducers/__tests__/patron-test.js +++ b/app/soapbox/reducers/__tests__/patron-test.js @@ -1,8 +1,29 @@ import reducer from '../patron'; -import { Map as ImmutableMap } from 'immutable'; +import { PATRON_ACCOUNT_FETCH_SUCCESS } from '../../actions/patron'; +import { Map as ImmutableMap, fromJS } from 'immutable'; describe('patron reducer', () => { it('should return the initial state', () => { expect(reducer(undefined, {})).toEqual(ImmutableMap()); }); + + describe('PATRON_ACCOUNT_FETCH_SUCCESS', () => { + it('should add the account', () => { + const action = { + type: PATRON_ACCOUNT_FETCH_SUCCESS, + account: { + url: 'https://gleasonator.com/users/alex', + is_patron: true, + }, + }; + const state = ImmutableMap(); + expect(reducer(state, action)).toEqual(fromJS({ + accounts: { + 'https://gleasonator.com/users/alex': { + is_patron: true, + }, + }, + })); + }); + }); }); diff --git a/app/soapbox/reducers/patron.js b/app/soapbox/reducers/patron.js index 459927bf9..ae639cdef 100644 --- a/app/soapbox/reducers/patron.js +++ b/app/soapbox/reducers/patron.js @@ -1,12 +1,22 @@ -import { PATRON_INSTANCE_FETCH_SUCCESS } from '../actions/patron'; +import { + PATRON_INSTANCE_FETCH_SUCCESS, + PATRON_ACCOUNT_FETCH_SUCCESS, +} from '../actions/patron'; import { Map as ImmutableMap, fromJS } from 'immutable'; const initialState = ImmutableMap(); +const normalizePatronAccount = (state, account) => { + const normalized = fromJS(account).deleteAll(['url']); + return state.setIn(['accounts', account.url], normalized); +}; + export default function patron(state = initialState, action) { switch(action.type) { case PATRON_INSTANCE_FETCH_SUCCESS: - return fromJS(action.instance); + return state.set('instance', fromJS(action.instance)); + case PATRON_ACCOUNT_FETCH_SUCCESS: + return normalizePatronAccount(state, action.account); default: return state; } diff --git a/app/soapbox/selectors/index.js b/app/soapbox/selectors/index.js index c8bc22424..d1f84330a 100644 --- a/app/soapbox/selectors/index.js +++ b/app/soapbox/selectors/index.js @@ -5,9 +5,19 @@ const getAccountBase = (state, id) => state.getIn(['accounts', id], null const getAccountCounters = (state, id) => state.getIn(['accounts_counters', id], null); const getAccountRelationship = (state, id) => state.getIn(['relationships', id], null); const getAccountMoved = (state, id) => state.getIn(['accounts', state.getIn(['accounts', id, 'moved'])]); +const getAccountPatron = (state, id) => { + const url = state.getIn(['accounts', id, 'url']); + return state.getIn(['patron', 'accounts', url]); +}; export const makeGetAccount = () => { - return createSelector([getAccountBase, getAccountCounters, getAccountRelationship, getAccountMoved], (base, counters, relationship, moved) => { + return createSelector([ + getAccountBase, + getAccountCounters, + getAccountRelationship, + getAccountMoved, + getAccountPatron, + ], (base, counters, relationship, moved, patron) => { if (base === null) { return null; } @@ -15,6 +25,7 @@ export const makeGetAccount = () => { return base.merge(counters).withMutations(map => { map.set('relationship', relationship); map.set('moved', moved); + map.set('patron', patron); }); }); };