kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Remove account_counters reducer and legacy follow actions
rodzic
ad1718b5f9
commit
75dbeb65b6
|
@ -19,12 +19,10 @@ import {
|
||||||
fetchFollowing,
|
fetchFollowing,
|
||||||
fetchFollowRequests,
|
fetchFollowRequests,
|
||||||
fetchRelationships,
|
fetchRelationships,
|
||||||
followAccount,
|
|
||||||
muteAccount,
|
muteAccount,
|
||||||
removeFromFollowers,
|
removeFromFollowers,
|
||||||
subscribeAccount,
|
subscribeAccount,
|
||||||
unblockAccount,
|
unblockAccount,
|
||||||
unfollowAccount,
|
|
||||||
unmuteAccount,
|
unmuteAccount,
|
||||||
unsubscribeAccount,
|
unsubscribeAccount,
|
||||||
} from '../accounts';
|
} from '../accounts';
|
||||||
|
@ -381,169 +379,6 @@ describe('fetchAccountByUsername()', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('followAccount()', () => {
|
|
||||||
describe('when logged out', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
const state = rootState.set('me', null);
|
|
||||||
store = mockStore(state);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should do nothing', async() => {
|
|
||||||
await store.dispatch(followAccount('1'));
|
|
||||||
const actions = store.getActions();
|
|
||||||
|
|
||||||
expect(actions).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when logged in', () => {
|
|
||||||
const id = '1';
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
const state = rootState.set('me', '123');
|
|
||||||
store = mockStore(state);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with a successful API request', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
__stub((mock) => {
|
|
||||||
mock.onPost(`/api/v1/accounts/${id}/follow`).reply(200, { success: true });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should dispatch the correct actions', async() => {
|
|
||||||
const expectedActions = [
|
|
||||||
{
|
|
||||||
type: 'ACCOUNT_FOLLOW_REQUEST',
|
|
||||||
id,
|
|
||||||
locked: false,
|
|
||||||
skipLoading: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'ACCOUNT_FOLLOW_SUCCESS',
|
|
||||||
relationship: { success: true },
|
|
||||||
alreadyFollowing: undefined,
|
|
||||||
skipLoading: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
await store.dispatch(followAccount(id));
|
|
||||||
const actions = store.getActions();
|
|
||||||
|
|
||||||
expect(actions).toEqual(expectedActions);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with an unsuccessful API request', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
__stub((mock) => {
|
|
||||||
mock.onPost(`/api/v1/accounts/${id}/follow`).networkError();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should dispatch the correct actions', async() => {
|
|
||||||
const expectedActions = [
|
|
||||||
{
|
|
||||||
type: 'ACCOUNT_FOLLOW_REQUEST',
|
|
||||||
id,
|
|
||||||
locked: false,
|
|
||||||
skipLoading: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'ACCOUNT_FOLLOW_FAIL',
|
|
||||||
error: new Error('Network Error'),
|
|
||||||
locked: false,
|
|
||||||
skipLoading: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
try {
|
|
||||||
await store.dispatch(followAccount(id));
|
|
||||||
} catch (e) {
|
|
||||||
const actions = store.getActions();
|
|
||||||
expect(actions).toEqual(expectedActions);
|
|
||||||
expect(e).toEqual(new Error('Network Error'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('unfollowAccount()', () => {
|
|
||||||
describe('when logged out', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
const state = rootState.set('me', null);
|
|
||||||
store = mockStore(state);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should do nothing', async() => {
|
|
||||||
await store.dispatch(unfollowAccount('1'));
|
|
||||||
const actions = store.getActions();
|
|
||||||
|
|
||||||
expect(actions).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when logged in', () => {
|
|
||||||
const id = '1';
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
const state = rootState.set('me', '123');
|
|
||||||
store = mockStore(state);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with a successful API request', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
__stub((mock) => {
|
|
||||||
mock.onPost(`/api/v1/accounts/${id}/unfollow`).reply(200, { success: true });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should dispatch the correct actions', async() => {
|
|
||||||
const expectedActions = [
|
|
||||||
{ type: 'ACCOUNT_UNFOLLOW_REQUEST', id: '1', skipLoading: true },
|
|
||||||
{
|
|
||||||
type: 'ACCOUNT_UNFOLLOW_SUCCESS',
|
|
||||||
relationship: { success: true },
|
|
||||||
statuses: ImmutableMap({}),
|
|
||||||
skipLoading: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
await store.dispatch(unfollowAccount(id));
|
|
||||||
const actions = store.getActions();
|
|
||||||
|
|
||||||
expect(actions).toEqual(expectedActions);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with an unsuccessful API request', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
__stub((mock) => {
|
|
||||||
mock.onPost(`/api/v1/accounts/${id}/unfollow`).networkError();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should dispatch the correct actions', async() => {
|
|
||||||
const expectedActions = [
|
|
||||||
{
|
|
||||||
type: 'ACCOUNT_UNFOLLOW_REQUEST',
|
|
||||||
id,
|
|
||||||
skipLoading: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'ACCOUNT_UNFOLLOW_FAIL',
|
|
||||||
error: new Error('Network Error'),
|
|
||||||
skipLoading: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
await store.dispatch(unfollowAccount(id));
|
|
||||||
const actions = store.getActions();
|
|
||||||
|
|
||||||
expect(actions).toEqual(expectedActions);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('blockAccount()', () => {
|
describe('blockAccount()', () => {
|
||||||
const id = '1';
|
const id = '1';
|
||||||
|
|
||||||
|
|
|
@ -23,14 +23,6 @@ const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST';
|
||||||
const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS';
|
const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS';
|
||||||
const ACCOUNT_FETCH_FAIL = 'ACCOUNT_FETCH_FAIL';
|
const ACCOUNT_FETCH_FAIL = 'ACCOUNT_FETCH_FAIL';
|
||||||
|
|
||||||
const ACCOUNT_FOLLOW_REQUEST = 'ACCOUNT_FOLLOW_REQUEST';
|
|
||||||
const ACCOUNT_FOLLOW_SUCCESS = 'ACCOUNT_FOLLOW_SUCCESS';
|
|
||||||
const ACCOUNT_FOLLOW_FAIL = 'ACCOUNT_FOLLOW_FAIL';
|
|
||||||
|
|
||||||
const ACCOUNT_UNFOLLOW_REQUEST = 'ACCOUNT_UNFOLLOW_REQUEST';
|
|
||||||
const ACCOUNT_UNFOLLOW_SUCCESS = 'ACCOUNT_UNFOLLOW_SUCCESS';
|
|
||||||
const ACCOUNT_UNFOLLOW_FAIL = 'ACCOUNT_UNFOLLOW_FAIL';
|
|
||||||
|
|
||||||
const ACCOUNT_BLOCK_REQUEST = 'ACCOUNT_BLOCK_REQUEST';
|
const ACCOUNT_BLOCK_REQUEST = 'ACCOUNT_BLOCK_REQUEST';
|
||||||
const ACCOUNT_BLOCK_SUCCESS = 'ACCOUNT_BLOCK_SUCCESS';
|
const ACCOUNT_BLOCK_SUCCESS = 'ACCOUNT_BLOCK_SUCCESS';
|
||||||
const ACCOUNT_BLOCK_FAIL = 'ACCOUNT_BLOCK_FAIL';
|
const ACCOUNT_BLOCK_FAIL = 'ACCOUNT_BLOCK_FAIL';
|
||||||
|
@ -227,81 +219,6 @@ const fetchAccountFail = (id: string | null, error: AxiosError) => ({
|
||||||
skipAlert: true,
|
skipAlert: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
type FollowAccountOpts = {
|
|
||||||
reblogs?: boolean
|
|
||||||
notify?: boolean
|
|
||||||
};
|
|
||||||
|
|
||||||
const followAccount = (id: string, options?: FollowAccountOpts) =>
|
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
|
||||||
if (!isLoggedIn(getState)) return null;
|
|
||||||
|
|
||||||
const alreadyFollowing = getState().relationships.get(id)?.following || undefined;
|
|
||||||
const locked = getState().accounts.get(id)?.locked || false;
|
|
||||||
|
|
||||||
dispatch(followAccountRequest(id, locked));
|
|
||||||
|
|
||||||
return api(getState)
|
|
||||||
.post(`/api/v1/accounts/${id}/follow`, options)
|
|
||||||
.then(response => dispatch(followAccountSuccess(response.data, alreadyFollowing)))
|
|
||||||
.catch(error => {
|
|
||||||
dispatch(followAccountFail(error, locked));
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const unfollowAccount = (id: string) =>
|
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
|
||||||
if (!isLoggedIn(getState)) return null;
|
|
||||||
|
|
||||||
dispatch(unfollowAccountRequest(id));
|
|
||||||
|
|
||||||
return api(getState)
|
|
||||||
.post(`/api/v1/accounts/${id}/unfollow`)
|
|
||||||
.then(response => dispatch(unfollowAccountSuccess(response.data, getState().statuses)))
|
|
||||||
.catch(error => dispatch(unfollowAccountFail(error)));
|
|
||||||
};
|
|
||||||
|
|
||||||
const followAccountRequest = (id: string, locked: boolean) => ({
|
|
||||||
type: ACCOUNT_FOLLOW_REQUEST,
|
|
||||||
id,
|
|
||||||
locked,
|
|
||||||
skipLoading: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const followAccountSuccess = (relationship: APIEntity, alreadyFollowing?: boolean) => ({
|
|
||||||
type: ACCOUNT_FOLLOW_SUCCESS,
|
|
||||||
relationship,
|
|
||||||
alreadyFollowing,
|
|
||||||
skipLoading: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const followAccountFail = (error: AxiosError, locked: boolean) => ({
|
|
||||||
type: ACCOUNT_FOLLOW_FAIL,
|
|
||||||
error,
|
|
||||||
locked,
|
|
||||||
skipLoading: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const unfollowAccountRequest = (id: string) => ({
|
|
||||||
type: ACCOUNT_UNFOLLOW_REQUEST,
|
|
||||||
id,
|
|
||||||
skipLoading: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const unfollowAccountSuccess = (relationship: APIEntity, statuses: ImmutableMap<string, Status>) => ({
|
|
||||||
type: ACCOUNT_UNFOLLOW_SUCCESS,
|
|
||||||
relationship,
|
|
||||||
statuses,
|
|
||||||
skipLoading: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const unfollowAccountFail = (error: AxiosError) => ({
|
|
||||||
type: ACCOUNT_UNFOLLOW_FAIL,
|
|
||||||
error,
|
|
||||||
skipLoading: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const blockAccount = (id: string) =>
|
const blockAccount = (id: string) =>
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||||
if (!isLoggedIn(getState)) return null;
|
if (!isLoggedIn(getState)) return null;
|
||||||
|
@ -988,12 +905,6 @@ export {
|
||||||
ACCOUNT_FETCH_REQUEST,
|
ACCOUNT_FETCH_REQUEST,
|
||||||
ACCOUNT_FETCH_SUCCESS,
|
ACCOUNT_FETCH_SUCCESS,
|
||||||
ACCOUNT_FETCH_FAIL,
|
ACCOUNT_FETCH_FAIL,
|
||||||
ACCOUNT_FOLLOW_REQUEST,
|
|
||||||
ACCOUNT_FOLLOW_SUCCESS,
|
|
||||||
ACCOUNT_FOLLOW_FAIL,
|
|
||||||
ACCOUNT_UNFOLLOW_REQUEST,
|
|
||||||
ACCOUNT_UNFOLLOW_SUCCESS,
|
|
||||||
ACCOUNT_UNFOLLOW_FAIL,
|
|
||||||
ACCOUNT_BLOCK_REQUEST,
|
ACCOUNT_BLOCK_REQUEST,
|
||||||
ACCOUNT_BLOCK_SUCCESS,
|
ACCOUNT_BLOCK_SUCCESS,
|
||||||
ACCOUNT_BLOCK_FAIL,
|
ACCOUNT_BLOCK_FAIL,
|
||||||
|
@ -1069,14 +980,6 @@ export {
|
||||||
fetchAccountRequest,
|
fetchAccountRequest,
|
||||||
fetchAccountSuccess,
|
fetchAccountSuccess,
|
||||||
fetchAccountFail,
|
fetchAccountFail,
|
||||||
followAccount,
|
|
||||||
unfollowAccount,
|
|
||||||
followAccountRequest,
|
|
||||||
followAccountSuccess,
|
|
||||||
followAccountFail,
|
|
||||||
unfollowAccountRequest,
|
|
||||||
unfollowAccountSuccess,
|
|
||||||
unfollowAccountFail,
|
|
||||||
blockAccount,
|
blockAccount,
|
||||||
unblockAccount,
|
unblockAccount,
|
||||||
blockAccountRequest,
|
blockAccountRequest,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Entities } from 'soapbox/entity-store/entities';
|
import { Entities } from 'soapbox/entity-store/entities';
|
||||||
import { useChangeEntity } from 'soapbox/entity-store/hooks';
|
import { useChangeEntity } from 'soapbox/entity-store/hooks';
|
||||||
|
import { useLoggedIn } from 'soapbox/hooks';
|
||||||
import { useApi } from 'soapbox/hooks/useApi';
|
import { useApi } from 'soapbox/hooks/useApi';
|
||||||
import { type Account } from 'soapbox/schemas';
|
import { type Account } from 'soapbox/schemas';
|
||||||
|
|
||||||
|
@ -8,8 +9,15 @@ function useChangeAccount() {
|
||||||
return { changeAccount };
|
return { changeAccount };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface FollowOpts {
|
||||||
|
reblogs?: boolean
|
||||||
|
notify?: boolean
|
||||||
|
languages?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
function useFollow() {
|
function useFollow() {
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
|
const { isLoggedIn } = useLoggedIn();
|
||||||
const { changeAccount } = useChangeAccount();
|
const { changeAccount } = useChangeAccount();
|
||||||
|
|
||||||
function incrementFollowers(accountId: string) {
|
function incrementFollowers(accountId: string) {
|
||||||
|
@ -26,7 +34,8 @@ function useFollow() {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function follow(accountId: string, options = {}) {
|
async function follow(accountId: string, options: FollowOpts = {}) {
|
||||||
|
if (!isLoggedIn) return;
|
||||||
incrementFollowers(accountId);
|
incrementFollowers(accountId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -36,11 +45,12 @@ function useFollow() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function unfollow(accountId: string, options = {}) {
|
async function unfollow(accountId: string) {
|
||||||
|
if (!isLoggedIn) return;
|
||||||
decrementFollowers(accountId);
|
decrementFollowers(accountId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await api.post(`/api/v1/accounts/${accountId}/unfollow`, options);
|
await api.post(`/api/v1/accounts/${accountId}/unfollow`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
incrementFollowers(accountId);
|
incrementFollowers(accountId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import React from 'react';
|
||||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
import { blockAccount, followAccount, pinAccount, removeFromFollowers, unblockAccount, unmuteAccount, unpinAccount } from 'soapbox/actions/accounts';
|
import { blockAccount, pinAccount, removeFromFollowers, unblockAccount, unmuteAccount, unpinAccount } from 'soapbox/actions/accounts';
|
||||||
import { mentionCompose, directCompose } from 'soapbox/actions/compose';
|
import { mentionCompose, directCompose } from 'soapbox/actions/compose';
|
||||||
import { blockDomain, unblockDomain } from 'soapbox/actions/domain-blocks';
|
import { blockDomain, unblockDomain } from 'soapbox/actions/domain-blocks';
|
||||||
import { openModal } from 'soapbox/actions/modals';
|
import { openModal } from 'soapbox/actions/modals';
|
||||||
|
@ -15,6 +15,7 @@ import { initMuteModal } from 'soapbox/actions/mutes';
|
||||||
import { initReport, ReportableEntities } from 'soapbox/actions/reports';
|
import { initReport, ReportableEntities } from 'soapbox/actions/reports';
|
||||||
import { setSearchAccount } from 'soapbox/actions/search';
|
import { setSearchAccount } from 'soapbox/actions/search';
|
||||||
import { getSettings } from 'soapbox/actions/settings';
|
import { getSettings } from 'soapbox/actions/settings';
|
||||||
|
import { useFollow } from 'soapbox/api/hooks';
|
||||||
import Badge from 'soapbox/components/badge';
|
import Badge from 'soapbox/components/badge';
|
||||||
import DropdownMenu, { Menu } from 'soapbox/components/dropdown-menu';
|
import DropdownMenu, { Menu } from 'soapbox/components/dropdown-menu';
|
||||||
import StillImage from 'soapbox/components/still-image';
|
import StillImage from 'soapbox/components/still-image';
|
||||||
|
@ -87,6 +88,7 @@ const Header: React.FC<IHeader> = ({ account }) => {
|
||||||
|
|
||||||
const features = useFeatures();
|
const features = useFeatures();
|
||||||
const ownAccount = useOwnAccount();
|
const ownAccount = useOwnAccount();
|
||||||
|
const { follow } = useFollow();
|
||||||
|
|
||||||
const { software } = useAppSelector((state) => parseVersion(state.instance.version));
|
const { software } = useAppSelector((state) => parseVersion(state.instance.version));
|
||||||
|
|
||||||
|
@ -154,9 +156,9 @@ const Header: React.FC<IHeader> = ({ account }) => {
|
||||||
|
|
||||||
const onReblogToggle = () => {
|
const onReblogToggle = () => {
|
||||||
if (account.relationship?.showing_reblogs) {
|
if (account.relationship?.showing_reblogs) {
|
||||||
dispatch(followAccount(account.id, { reblogs: false }));
|
follow(account.id, { reblogs: false });
|
||||||
} else {
|
} else {
|
||||||
dispatch(followAccount(account.id, { reblogs: true }));
|
follow(account.id, { reblogs: true });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,6 @@ import React from 'react';
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
followAccount,
|
|
||||||
unfollowAccount,
|
|
||||||
blockAccount,
|
blockAccount,
|
||||||
unblockAccount,
|
unblockAccount,
|
||||||
muteAccount,
|
muteAccount,
|
||||||
|
@ -12,8 +10,9 @@ import {
|
||||||
rejectFollowRequest,
|
rejectFollowRequest,
|
||||||
} from 'soapbox/actions/accounts';
|
} from 'soapbox/actions/accounts';
|
||||||
import { openModal } from 'soapbox/actions/modals';
|
import { openModal } from 'soapbox/actions/modals';
|
||||||
|
import { useFollow } from 'soapbox/api/hooks';
|
||||||
import { Button, HStack } from 'soapbox/components/ui';
|
import { Button, HStack } from 'soapbox/components/ui';
|
||||||
import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
|
import { useAppDispatch, useFeatures, useLoggedIn } from 'soapbox/hooks';
|
||||||
|
|
||||||
import type { Account } from 'soapbox/schemas';
|
import type { Account } from 'soapbox/schemas';
|
||||||
import type { Account as AccountEntity } from 'soapbox/types/entities';
|
import type { Account as AccountEntity } from 'soapbox/types/entities';
|
||||||
|
@ -53,13 +52,14 @@ const ActionButton: React.FC<IActionButton> = ({ account, actionType, small }) =
|
||||||
const features = useFeatures();
|
const features = useFeatures();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const me = useAppSelector((state) => state.me);
|
const { isLoggedIn, me } = useLoggedIn();
|
||||||
|
const { follow, unfollow } = useFollow();
|
||||||
|
|
||||||
const handleFollow = () => {
|
const handleFollow = () => {
|
||||||
if (account.relationship?.following || account.relationship?.requested) {
|
if (account.relationship?.following || account.relationship?.requested) {
|
||||||
dispatch(unfollowAccount(account.id));
|
unfollow(account.id);
|
||||||
} else {
|
} else {
|
||||||
dispatch(followAccount(account.id));
|
follow(account.id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -187,7 +187,7 @@ const ActionButton: React.FC<IActionButton> = ({ account, actionType, small }) =
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!me) {
|
if (!isLoggedIn) {
|
||||||
return renderLoggedOut();
|
return renderLoggedOut();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,10 @@ import React from 'react';
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
followAccount,
|
|
||||||
subscribeAccount,
|
subscribeAccount,
|
||||||
unsubscribeAccount,
|
unsubscribeAccount,
|
||||||
} from 'soapbox/actions/accounts';
|
} from 'soapbox/actions/accounts';
|
||||||
|
import { useFollow } from 'soapbox/api/hooks';
|
||||||
import { IconButton } from 'soapbox/components/ui';
|
import { IconButton } from 'soapbox/components/ui';
|
||||||
import { useAppDispatch, useFeatures } from 'soapbox/hooks';
|
import { useAppDispatch, useFeatures } from 'soapbox/hooks';
|
||||||
import toast from 'soapbox/toast';
|
import toast from 'soapbox/toast';
|
||||||
|
@ -29,6 +29,7 @@ const SubscriptionButton = ({ account }: ISubscriptionButton) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const features = useFeatures();
|
const features = useFeatures();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const { follow } = useFollow();
|
||||||
|
|
||||||
const isFollowing = account.relationship?.following;
|
const isFollowing = account.relationship?.following;
|
||||||
const isRequested = account.relationship?.requested;
|
const isRequested = account.relationship?.requested;
|
||||||
|
@ -53,11 +54,11 @@ const SubscriptionButton = ({ account }: ISubscriptionButton) => {
|
||||||
|
|
||||||
const onNotifyToggle = () => {
|
const onNotifyToggle = () => {
|
||||||
if (account.relationship?.notifying) {
|
if (account.relationship?.notifying) {
|
||||||
dispatch(followAccount(account.id, { notify: false } as any))
|
follow(account.id, { notify: false })
|
||||||
?.then(() => onUnsubscribeSuccess())
|
?.then(() => onUnsubscribeSuccess())
|
||||||
.catch(() => onUnsubscribeFailure());
|
.catch(() => onUnsubscribeFailure());
|
||||||
} else {
|
} else {
|
||||||
dispatch(followAccount(account.id, { notify: true } as any))
|
follow(account.id, { notify: true })
|
||||||
?.then(() => onSubscribeSuccess())
|
?.then(() => onSubscribeSuccess())
|
||||||
.catch(() => onSubscribeFailure());
|
.catch(() => onSubscribeFailure());
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ export { useFeatures } from './useFeatures';
|
||||||
export { useInstance } from './useInstance';
|
export { useInstance } from './useInstance';
|
||||||
export { useLoading } from './useLoading';
|
export { useLoading } from './useLoading';
|
||||||
export { useLocale } from './useLocale';
|
export { useLocale } from './useLocale';
|
||||||
|
export { useLoggedIn } from './useLoggedIn';
|
||||||
export { useOnScreen } from './useOnScreen';
|
export { useOnScreen } from './useOnScreen';
|
||||||
export { useOwnAccount } from './useOwnAccount';
|
export { useOwnAccount } from './useOwnAccount';
|
||||||
export { usePrevious } from './usePrevious';
|
export { usePrevious } from './usePrevious';
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { useAppSelector } from './useAppSelector';
|
||||||
|
|
||||||
|
function useLoggedIn() {
|
||||||
|
const me = useAppSelector(state => state.me);
|
||||||
|
return {
|
||||||
|
isLoggedIn: typeof me === 'string',
|
||||||
|
isLoginLoading: me === null,
|
||||||
|
isLoginFailed: me === false,
|
||||||
|
me,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { useLoggedIn };
|
|
@ -1,37 +0,0 @@
|
||||||
import { Map as ImmutableMap } from 'immutable';
|
|
||||||
|
|
||||||
import reducer from '../accounts-counters';
|
|
||||||
// import { ACCOUNT_FOLLOW_SUCCESS, ACCOUNT_UNFOLLOW_SUCCESS } from 'soapbox/actions/accounts';
|
|
||||||
// import relationship from 'soapbox/__fixtures__/relationship.json';
|
|
||||||
// import accounts_counter_initial from 'soapbox/__fixtures__/accounts_counter_initial.json';
|
|
||||||
// import accounts_counter_unfollow from 'soapbox/__fixtures__/accounts_counter_unfollow.json';
|
|
||||||
// import accounts_counter_follow from 'soapbox/__fixtures__/accounts_counter_follow.json';
|
|
||||||
|
|
||||||
describe('accounts_counters reducer', () => {
|
|
||||||
it('should return the initial state', () => {
|
|
||||||
expect(reducer(undefined, {} as any)).toEqual(ImmutableMap());
|
|
||||||
});
|
|
||||||
|
|
||||||
// it('should handle ACCOUNT_FOLLOW_SUCCESS', () => {
|
|
||||||
// const state = ImmutableList([accounts_counter_initial]);
|
|
||||||
// const action = {
|
|
||||||
// type: ACCOUNT_FOLLOW_SUCCESS,
|
|
||||||
// relationship: relationship,
|
|
||||||
// alreadyFollowing: false,
|
|
||||||
// };
|
|
||||||
// expect(reducer(state, action)).toEqual(
|
|
||||||
// ImmutableList([ accounts_counter_follow ]));
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// it('should handle ACCOUNT_UNFOLLOW_SUCCESS', () => {
|
|
||||||
// const state = ImmutableList([accounts_counter_initial]);
|
|
||||||
// const action = {
|
|
||||||
// type: ACCOUNT_UNFOLLOW_SUCCESS,
|
|
||||||
// relationship: relationship,
|
|
||||||
// alreadyFollowing: true,
|
|
||||||
// };
|
|
||||||
// expect(reducer(state, action)).toEqual(
|
|
||||||
// ImmutableList([accounts_counter_unfollow]));
|
|
||||||
// });
|
|
||||||
|
|
||||||
});
|
|
|
@ -1,64 +0,0 @@
|
||||||
import { List as ImmutableList, Map as ImmutableMap, Record as ImmutableRecord } from 'immutable';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ACCOUNT_FOLLOW_SUCCESS,
|
|
||||||
ACCOUNT_UNFOLLOW_SUCCESS,
|
|
||||||
} from 'soapbox/actions/accounts';
|
|
||||||
import { ACCOUNT_IMPORT, ACCOUNTS_IMPORT } from 'soapbox/actions/importer';
|
|
||||||
import { STREAMING_FOLLOW_RELATIONSHIPS_UPDATE } from 'soapbox/actions/streaming';
|
|
||||||
|
|
||||||
import type { AnyAction } from 'redux';
|
|
||||||
import type { APIEntity } from 'soapbox/types/entities';
|
|
||||||
|
|
||||||
const CounterRecord = ImmutableRecord({
|
|
||||||
followers_count: 0,
|
|
||||||
following_count: 0,
|
|
||||||
statuses_count: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
type Counter = ReturnType<typeof CounterRecord>;
|
|
||||||
type State = ImmutableMap<string, Counter>;
|
|
||||||
type APIEntities = Array<APIEntity>;
|
|
||||||
|
|
||||||
const normalizeAccount = (state: State, account: APIEntity) => state.set(account.id, CounterRecord({
|
|
||||||
followers_count: account.followers_count,
|
|
||||||
following_count: account.following_count,
|
|
||||||
statuses_count: account.statuses_count,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const normalizeAccounts = (state: State, accounts: ImmutableList<APIEntities>) => {
|
|
||||||
accounts.forEach(account => {
|
|
||||||
state = normalizeAccount(state, account);
|
|
||||||
});
|
|
||||||
|
|
||||||
return state;
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateFollowCounters = (state: State, counterUpdates: APIEntities) => {
|
|
||||||
return state.withMutations(state => {
|
|
||||||
counterUpdates.forEach((counterUpdate) => {
|
|
||||||
state.update(counterUpdate.id, CounterRecord(), counters => counters.merge({
|
|
||||||
followers_count: counterUpdate.follower_count,
|
|
||||||
following_count: counterUpdate.following_count,
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function accountsCounters(state: State = ImmutableMap<string, Counter>(), action: AnyAction) {
|
|
||||||
switch (action.type) {
|
|
||||||
case ACCOUNT_IMPORT:
|
|
||||||
return normalizeAccount(state, action.account);
|
|
||||||
case ACCOUNTS_IMPORT:
|
|
||||||
return normalizeAccounts(state, action.accounts);
|
|
||||||
case ACCOUNT_FOLLOW_SUCCESS:
|
|
||||||
return action.alreadyFollowing ? state :
|
|
||||||
state.updateIn([action.relationship.id, 'followers_count'], 0, (count) => typeof count === 'number' ? count + 1 : 0);
|
|
||||||
case ACCOUNT_UNFOLLOW_SUCCESS:
|
|
||||||
return state.updateIn([action.relationship.id, 'followers_count'], 0, (count) => typeof count === 'number' ? Math.max(0, count - 1) : 0);
|
|
||||||
case STREAMING_FOLLOW_RELATIONSHIPS_UPDATE:
|
|
||||||
return updateFollowCounters(state, [action.follower, action.following]);
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,7 +9,6 @@ import entities from 'soapbox/entity-store/reducer';
|
||||||
import { immutableizeStore, type LegacyStore } from 'soapbox/utils/legacy';
|
import { immutableizeStore, type LegacyStore } from 'soapbox/utils/legacy';
|
||||||
|
|
||||||
import account_notes from './account-notes';
|
import account_notes from './account-notes';
|
||||||
import accounts_counters from './accounts-counters';
|
|
||||||
import accounts_meta from './accounts-meta';
|
import accounts_meta from './accounts-meta';
|
||||||
import admin from './admin';
|
import admin from './admin';
|
||||||
import admin_announcements from './admin-announcements';
|
import admin_announcements from './admin-announcements';
|
||||||
|
@ -78,7 +77,6 @@ import type { Account } from 'soapbox/schemas';
|
||||||
const reducers = {
|
const reducers = {
|
||||||
accounts: ((state: any = {}) => state) as (state: any) => EntityStore<Account> & LegacyStore<Account>,
|
accounts: ((state: any = {}) => state) as (state: any) => EntityStore<Account> & LegacyStore<Account>,
|
||||||
account_notes,
|
account_notes,
|
||||||
accounts_counters,
|
|
||||||
accounts_meta,
|
accounts_meta,
|
||||||
admin,
|
admin,
|
||||||
admin_announcements,
|
admin_announcements,
|
||||||
|
|
|
@ -6,12 +6,6 @@ import { type Relationship, relationshipSchema } from 'soapbox/schemas';
|
||||||
|
|
||||||
import { ACCOUNT_NOTE_SUBMIT_SUCCESS } from '../actions/account-notes';
|
import { ACCOUNT_NOTE_SUBMIT_SUCCESS } from '../actions/account-notes';
|
||||||
import {
|
import {
|
||||||
ACCOUNT_FOLLOW_SUCCESS,
|
|
||||||
ACCOUNT_FOLLOW_REQUEST,
|
|
||||||
ACCOUNT_FOLLOW_FAIL,
|
|
||||||
ACCOUNT_UNFOLLOW_SUCCESS,
|
|
||||||
ACCOUNT_UNFOLLOW_REQUEST,
|
|
||||||
ACCOUNT_UNFOLLOW_FAIL,
|
|
||||||
ACCOUNT_BLOCK_SUCCESS,
|
ACCOUNT_BLOCK_SUCCESS,
|
||||||
ACCOUNT_UNBLOCK_SUCCESS,
|
ACCOUNT_UNBLOCK_SUCCESS,
|
||||||
ACCOUNT_MUTE_SUCCESS,
|
ACCOUNT_MUTE_SUCCESS,
|
||||||
|
@ -101,16 +95,16 @@ export default function relationships(state: State = ImmutableMap<string, Relati
|
||||||
return importPleromaAccount(state, action.account);
|
return importPleromaAccount(state, action.account);
|
||||||
case ACCOUNTS_IMPORT:
|
case ACCOUNTS_IMPORT:
|
||||||
return importPleromaAccounts(state, action.accounts);
|
return importPleromaAccounts(state, action.accounts);
|
||||||
case ACCOUNT_FOLLOW_REQUEST:
|
// case ACCOUNT_FOLLOW_REQUEST:
|
||||||
return state.setIn([action.id, 'following'], true);
|
// return state.setIn([action.id, 'following'], true);
|
||||||
case ACCOUNT_FOLLOW_FAIL:
|
// case ACCOUNT_FOLLOW_FAIL:
|
||||||
return state.setIn([action.id, 'following'], false);
|
// return state.setIn([action.id, 'following'], false);
|
||||||
case ACCOUNT_UNFOLLOW_REQUEST:
|
// case ACCOUNT_UNFOLLOW_REQUEST:
|
||||||
return state.setIn([action.id, 'following'], false);
|
// return state.setIn([action.id, 'following'], false);
|
||||||
case ACCOUNT_UNFOLLOW_FAIL:
|
// case ACCOUNT_UNFOLLOW_FAIL:
|
||||||
return state.setIn([action.id, 'following'], true);
|
// return state.setIn([action.id, 'following'], true);
|
||||||
case ACCOUNT_FOLLOW_SUCCESS:
|
// case ACCOUNT_FOLLOW_SUCCESS:
|
||||||
case ACCOUNT_UNFOLLOW_SUCCESS:
|
// case ACCOUNT_UNFOLLOW_SUCCESS:
|
||||||
case ACCOUNT_BLOCK_SUCCESS:
|
case ACCOUNT_BLOCK_SUCCESS:
|
||||||
case ACCOUNT_UNBLOCK_SUCCESS:
|
case ACCOUNT_UNBLOCK_SUCCESS:
|
||||||
case ACCOUNT_MUTE_SUCCESS:
|
case ACCOUNT_MUTE_SUCCESS:
|
||||||
|
|
|
@ -10,7 +10,6 @@ import sample from 'lodash/sample';
|
||||||
import {
|
import {
|
||||||
ACCOUNT_BLOCK_SUCCESS,
|
ACCOUNT_BLOCK_SUCCESS,
|
||||||
ACCOUNT_MUTE_SUCCESS,
|
ACCOUNT_MUTE_SUCCESS,
|
||||||
ACCOUNT_UNFOLLOW_SUCCESS,
|
|
||||||
} from '../actions/accounts';
|
} from '../actions/accounts';
|
||||||
import {
|
import {
|
||||||
STATUS_CREATE_REQUEST,
|
STATUS_CREATE_REQUEST,
|
||||||
|
@ -204,11 +203,11 @@ const buildReferencesTo = (statuses: ImmutableMap<string, Status>, status: Statu
|
||||||
.map(statusToReference) as ImmutableMap<string, [string, string]>
|
.map(statusToReference) as ImmutableMap<string, [string, string]>
|
||||||
);
|
);
|
||||||
|
|
||||||
const filterTimeline = (state: State, timelineId: string, relationship: APIEntity, statuses: ImmutableList<ImmutableMap<string, any>>) =>
|
// const filterTimeline = (state: State, timelineId: string, relationship: APIEntity, statuses: ImmutableList<ImmutableMap<string, any>>) =>
|
||||||
state.updateIn([timelineId, 'items'], ImmutableOrderedSet(), (ids) =>
|
// state.updateIn([timelineId, 'items'], ImmutableOrderedSet(), (ids) =>
|
||||||
(ids as ImmutableOrderedSet<string>).filterNot(statusId =>
|
// (ids as ImmutableOrderedSet<string>).filterNot(statusId =>
|
||||||
statuses.getIn([statusId, 'account']) === relationship.id,
|
// statuses.getIn([statusId, 'account']) === relationship.id,
|
||||||
));
|
// ));
|
||||||
|
|
||||||
const filterTimelines = (state: State, relationship: APIEntity, statuses: ImmutableMap<string, Status>) => {
|
const filterTimelines = (state: State, relationship: APIEntity, statuses: ImmutableMap<string, Status>) => {
|
||||||
return state.withMutations(state => {
|
return state.withMutations(state => {
|
||||||
|
@ -356,8 +355,8 @@ export default function timelines(state: State = initialState, action: AnyAction
|
||||||
case ACCOUNT_BLOCK_SUCCESS:
|
case ACCOUNT_BLOCK_SUCCESS:
|
||||||
case ACCOUNT_MUTE_SUCCESS:
|
case ACCOUNT_MUTE_SUCCESS:
|
||||||
return filterTimelines(state, action.relationship, action.statuses);
|
return filterTimelines(state, action.relationship, action.statuses);
|
||||||
case ACCOUNT_UNFOLLOW_SUCCESS:
|
// case ACCOUNT_UNFOLLOW_SUCCESS:
|
||||||
return filterTimeline(state, 'home', action.relationship, action.statuses);
|
// return filterTimeline(state, 'home', action.relationship, action.statuses);
|
||||||
case TIMELINE_SCROLL_TOP:
|
case TIMELINE_SCROLL_TOP:
|
||||||
return updateTop(state, action.timeline, action.top);
|
return updateTop(state, action.timeline, action.top);
|
||||||
case TIMELINE_CONNECT:
|
case TIMELINE_CONNECT:
|
||||||
|
|
|
@ -22,7 +22,6 @@ import type { Filter as FilterEntity, Notification, Status, Group } from 'soapbo
|
||||||
const normalizeId = (id: any): string => typeof id === 'string' ? id : '';
|
const normalizeId = (id: any): string => typeof id === 'string' ? id : '';
|
||||||
|
|
||||||
const getAccountBase = (state: RootState, id: string) => state.accounts.get(id);
|
const getAccountBase = (state: RootState, id: string) => state.accounts.get(id);
|
||||||
const getAccountCounters = (state: RootState, id: string) => state.accounts_counters.get(id);
|
|
||||||
const getAccountRelationship = (state: RootState, id: string) => state.relationships.get(id);
|
const getAccountRelationship = (state: RootState, id: string) => state.relationships.get(id);
|
||||||
const getAccountMoved = (state: RootState, id: string) => state.accounts.get(state.accounts.get(id)?.moved || '');
|
const getAccountMoved = (state: RootState, id: string) => state.accounts.get(state.accounts.get(id)?.moved || '');
|
||||||
const getAccountMeta = (state: RootState, id: string) => state.accounts_meta.get(id);
|
const getAccountMeta = (state: RootState, id: string) => state.accounts_meta.get(id);
|
||||||
|
@ -35,13 +34,12 @@ const getAccountPatron = (state: RootState, id: string) => {
|
||||||
export const makeGetAccount = () => {
|
export const makeGetAccount = () => {
|
||||||
return createSelector([
|
return createSelector([
|
||||||
getAccountBase,
|
getAccountBase,
|
||||||
getAccountCounters,
|
|
||||||
getAccountRelationship,
|
getAccountRelationship,
|
||||||
getAccountMoved,
|
getAccountMoved,
|
||||||
getAccountMeta,
|
getAccountMeta,
|
||||||
getAccountAdminData,
|
getAccountAdminData,
|
||||||
getAccountPatron,
|
getAccountPatron,
|
||||||
], (base, counters, relationship, moved, meta, admin, patron) => {
|
], (base, relationship, moved, meta, admin, patron) => {
|
||||||
if (!base) return null;
|
if (!base) return null;
|
||||||
base.relationship = base.relationship ?? relationship;
|
base.relationship = base.relationship ?? relationship;
|
||||||
return base;
|
return base;
|
||||||
|
|
Ładowanie…
Reference in New Issue