diff --git a/src/actions/interactions.ts b/src/actions/interactions.ts index 07ceb8f40..b18b8f3a2 100644 --- a/src/actions/interactions.ts +++ b/src/actions/interactions.ts @@ -88,6 +88,9 @@ const ZAPS_FETCH_REQUEST = 'ZAPS_FETCH_REQUEST'; const ZAPS_FETCH_SUCCESS = 'ZAPS_FETCH_SUCCESS'; const ZAPS_FETCH_FAIL = 'ZAPS_FETCH_FAIL'; +const ZAPS_EXPAND_SUCCESS = 'ZAPS_EXPAND_SUCCESS'; +const ZAPS_EXPAND_FAIL = 'ZAPS_EXPAND_FAIL'; + const messages = defineMessages({ bookmarkAdded: { id: 'status.bookmarked', defaultMessage: 'Bookmark added.' }, bookmarkRemoved: { id: 'status.unbookmarked', defaultMessage: 'Bookmark removed.' }, @@ -634,8 +637,9 @@ const fetchZaps = (id: string) => dispatch(fetchZapsRequest(id)); api(getState).get(`/api/v1/ditto/statuses/${id}/zapped_by`).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts((response.data as APIEntity[]).map(({ account }) => account).flat())); - dispatch(fetchZapsSuccess(id, response.data)); + dispatch(fetchZapsSuccess(id, response.data, next ? next.uri : null)); }).catch(error => { dispatch(fetchZapsFail(id, error)); }); @@ -646,10 +650,11 @@ const fetchZapsRequest = (id: string) => ({ id, }); -const fetchZapsSuccess = (id: string, zaps: APIEntity[]) => ({ +const fetchZapsSuccess = (id: string, zaps: APIEntity[], next: string | null) => ({ type: ZAPS_FETCH_SUCCESS, id, zaps, + next, }); const fetchZapsFail = (id: string, error: unknown) => ({ @@ -658,6 +663,31 @@ const fetchZapsFail = (id: string, error: unknown) => ({ error, }); +const expandZaps = (id: string, path: string) => + (dispatch: AppDispatch, getState: () => RootState) => { + api(getState).get(path).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(importFetchedAccounts(response.data.map((item: APIEntity) => item.account))); + dispatch(fetchRelationships(response.data.map((item: APIEntity) => item.account.id))); + dispatch(expandZapsSuccess(id, response.data, next ? next.uri : null)); + }).catch(error => { + dispatch(expandZapsFail(id, error)); + }); + }; + +const expandZapsSuccess = (id: string, zaps: APIEntity[], next: string | null) => ({ + type: ZAPS_EXPAND_SUCCESS, + id, + zaps, + next, +}); + +const expandZapsFail = (id: string, error: unknown) => ({ + type: ZAPS_EXPAND_FAIL, + id, + error, +}); + const pin = (status: StatusEntity) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; @@ -838,6 +868,8 @@ export { ZAPS_FETCH_REQUEST, ZAPS_FETCH_SUCCESS, ZAPS_FETCH_FAIL, + ZAPS_EXPAND_SUCCESS, + ZAPS_EXPAND_FAIL, reblog, unreblog, toggleReblog, @@ -909,4 +941,5 @@ export { remoteInteractionFail, zap, fetchZaps, + expandZaps, }; diff --git a/src/features/ui/components/modals/zaps-modal.tsx b/src/features/ui/components/modals/zaps-modal.tsx index e5cec90de..e6db04c9b 100644 --- a/src/features/ui/components/modals/zaps-modal.tsx +++ b/src/features/ui/components/modals/zaps-modal.tsx @@ -2,7 +2,7 @@ import { List as ImmutableList } from 'immutable'; import React, { useEffect, useMemo } from 'react'; import { FormattedMessage } from 'react-intl'; -import { fetchZaps } from 'soapbox/actions/interactions'; +import { fetchZaps, expandZaps } from 'soapbox/actions/interactions'; import ScrollableList from 'soapbox/components/scrollable-list'; import { Modal, Spinner, Text } from 'soapbox/components/ui'; import AccountContainer from 'soapbox/containers/account-container'; @@ -23,7 +23,7 @@ interface IZapsModal { const ZapsModal: React.FC = ({ onClose, statusId }) => { const dispatch = useAppDispatch(); const zaps = useAppSelector((state) => state.user_lists.zapped_by.get(statusId)?.items); - + const next = useAppSelector((state) => state.user_lists.zapped_by.get(statusId)?.next); const accounts = useMemo((): ImmutableList | undefined => { if (!zaps) return; @@ -43,6 +43,13 @@ const ZapsModal: React.FC = ({ onClose, statusId }) => { onClose('ZAPS'); }; + const handleLoadMore = () => { + if (next) { + console.log('next, zaps modal: ', next); + dispatch(expandZaps(statusId, next!)); + } + }; + let body; if (!zaps || !accounts) { @@ -58,14 +65,16 @@ const ZapsModal: React.FC = ({ onClose, statusId }) => { itemClassName='pb-3' style={{ height: '80vh' }} useWindowScroll={false} + onLoadMore={handleLoadMore} + hasMore={!!next} > - {accounts.map((account) => { + {accounts.map((account, index) => { return ( -
+
{shortNumberFormat(account.amount / 1000)} - +
); }, diff --git a/src/reducers/user-lists.ts b/src/reducers/user-lists.ts index cf420c299..97e1def39 100644 --- a/src/reducers/user-lists.ts +++ b/src/reducers/user-lists.ts @@ -2,6 +2,7 @@ import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet, Record as ImmutableRecord, + List as ImmutableList, } from 'immutable'; import { AnyAction } from 'redux'; @@ -65,6 +66,7 @@ import { DISLIKES_FETCH_SUCCESS, REACTIONS_FETCH_SUCCESS, ZAPS_FETCH_SUCCESS, + ZAPS_EXPAND_SUCCESS, } from 'soapbox/actions/interactions'; import { NOTIFICATIONS_UPDATE, @@ -98,7 +100,8 @@ export const ZapRecord = ImmutableRecord({ }); const ZapListRecord = ImmutableRecord({ - items: ImmutableOrderedSet(), + next: null as string | null, + items: ImmutableList(), isLoading: false, }); @@ -203,11 +206,22 @@ export default function userLists(state = ReducerRecord(), action: AnyAction) { })); case ZAPS_FETCH_SUCCESS: return state.setIn(['zapped_by', action.id], ZapListRecord({ - items: ImmutableOrderedSet(action.zaps.map(({ account, ...zap }: APIEntity) => ZapRecord({ + items: ImmutableList(action.zaps.map(({ account, ...zap }: APIEntity) => ZapRecord({ ...zap, account: account.id, - }))), + }))), next: action.next, })); + case ZAPS_EXPAND_SUCCESS: + return state.updateIn(['zapped_by', action.id], map => { + return (map as List) + .set('next', action.next) + .set('isLoading', false) + .update('items', list => (list as Items).concat(ImmutableList(action.zaps.map(({ account, ...zap }: APIEntity) => ZapRecord({ + ...zap, + account: account.id, + }))))); + }); + case NOTIFICATIONS_UPDATE: return action.notification.type === 'follow_request' ? normalizeFollowRequest(state, action.notification) : state; case FOLLOW_REQUESTS_FETCH_SUCCESS: