pl-fe: kmyblue circles management
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
@ -168,7 +168,7 @@ import type {
|
||||
GetChatMessagesParams,
|
||||
GetChatsParams,
|
||||
} from './params/chats';
|
||||
import type { GetCircleStatusesParams } from './params/circles';
|
||||
import type { GetCircleAccountsParams, GetCircleStatusesParams } from './params/circles';
|
||||
import type { UpdateFileParams } from './params/drive';
|
||||
import type {
|
||||
CreateEventParams,
|
||||
@ -5620,6 +5620,39 @@ class PlApiClient {
|
||||
return response.json;
|
||||
},
|
||||
|
||||
/**
|
||||
* View accounts in a circle
|
||||
* Requires features{@link Features.circles}.
|
||||
*/
|
||||
getCircleAccounts: async (circleId: string, params?: GetCircleAccountsParams) =>
|
||||
this.#paginatedGet(`/api/v1/circles/${circleId}/accounts`, { params }, accountSchema),
|
||||
|
||||
/**
|
||||
* Add accounts to a circle
|
||||
* Add accounts to the given circle. Note that the user must be following these accounts.
|
||||
* Requires features{@link Features.circles}.
|
||||
*/
|
||||
addCircleAccounts: async (circleId: string, accountIds: string[]) => {
|
||||
const response = await this.request(`/api/v1/circles/${circleId}/accounts`, {
|
||||
method: 'POST', body: { account_ids: accountIds },
|
||||
});
|
||||
|
||||
return response.json as {};
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove accounts from circle
|
||||
* Remove accounts from the given circle.
|
||||
* Requires features{@link Features.circles}.
|
||||
*/
|
||||
deleteCircleAccounts: async (circleId: string, accountIds: string[]) => {
|
||||
const response = await this.request(`/api/v1/circles/${circleId}/accounts`, {
|
||||
method: 'DELETE', body: { account_ids: accountIds },
|
||||
});
|
||||
|
||||
return response.json as {};
|
||||
},
|
||||
|
||||
getCircleStatuses: (circleId: string, params: GetCircleStatusesParams) =>
|
||||
this.#paginatedGet(`/api/v1/circles/${circleId}/statuses`, { params }, statusSchema),
|
||||
};
|
||||
|
||||
@ -5,6 +5,12 @@ import { PaginationParams } from './common';
|
||||
*/
|
||||
type GetCircleStatusesParams = PaginationParams;
|
||||
|
||||
/**
|
||||
* @category Request params
|
||||
*/
|
||||
type GetCircleAccountsParams = PaginationParams;
|
||||
|
||||
export type {
|
||||
GetCircleStatusesParams,
|
||||
GetCircleAccountsParams,
|
||||
};
|
||||
|
||||
@ -2,6 +2,7 @@ export * from './accounts';
|
||||
export * from './admin';
|
||||
export * from './apps';
|
||||
export * from './chats';
|
||||
export * from './circles';
|
||||
export type { PaginationParams } from './common';
|
||||
export * from './events';
|
||||
export * from './filtering';
|
||||
|
||||
@ -9,6 +9,7 @@ import { importEntities } from './importer';
|
||||
import type {
|
||||
Account as BaseAccount,
|
||||
GetAccountStatusesParams,
|
||||
GetCircleStatusesParams,
|
||||
GroupTimelineParams,
|
||||
HashtagTimelineParams,
|
||||
HomeTimelineParams,
|
||||
@ -260,6 +261,21 @@ const fetchListTimeline = (listId: string, expand = false, done = noOp) =>
|
||||
return dispatch(handleTimelineExpand(timelineId, fn, false, done));
|
||||
};
|
||||
|
||||
const fetchCircleTimeline = (circleId: string, expand = false, done = noOp) =>
|
||||
async (dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
const timelineId = `circle:${circleId}`;
|
||||
|
||||
const params: GetCircleStatusesParams = {};
|
||||
// if (useSettingsStore.getState().settings.autoTranslate) params.language = getLocale();
|
||||
|
||||
if (expand && state.timelines[timelineId]?.isLoading) return;
|
||||
|
||||
const fn = (expand && state.timelines[timelineId]?.next?.()) || getClient(state).circles.getCircleStatuses(circleId, params);
|
||||
|
||||
return dispatch(handleTimelineExpand(timelineId, fn, false, done));
|
||||
};
|
||||
|
||||
const fetchGroupTimeline = (groupId: string, { only_media, limit }: Record<string, any> = {}, expand = false, done = noOp) =>
|
||||
async (dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
@ -378,6 +394,7 @@ export {
|
||||
fetchBubbleTimeline,
|
||||
fetchAccountTimeline,
|
||||
fetchListTimeline,
|
||||
fetchCircleTimeline,
|
||||
fetchGroupTimeline,
|
||||
fetchHashtagTimeline,
|
||||
fetchLinkTimeline,
|
||||
|
||||
@ -12,6 +12,7 @@ const MODAL_COMPONENTS = {
|
||||
ALT_TEXT: lazy(() => import('pl-fe/modals/alt-text-modal')),
|
||||
BIRTHDAYS: lazy(() => import('pl-fe/modals/birthdays-modal')),
|
||||
BOOST: lazy(() => import('pl-fe/modals/boost-modal')),
|
||||
CIRCLE_EDITOR: lazy(() => import('pl-fe/modals/circle-editor-modal')),
|
||||
COMPARE_HISTORY: lazy(() => import('pl-fe/modals/compare-history-modal')),
|
||||
COMPONENT: lazy(() => import('pl-fe/modals/component-modal')),
|
||||
COMPOSE: lazy(() => import('pl-fe/modals/compose-modal')),
|
||||
|
||||
@ -67,6 +67,7 @@ import {
|
||||
ChatWidget,
|
||||
Circle,
|
||||
Circles,
|
||||
CircleTimeline,
|
||||
CommunityTimeline,
|
||||
ComposeEvent,
|
||||
Conversations,
|
||||
@ -249,6 +250,7 @@ const SwitchingColumnsArea: React.FC<ISwitchingColumnsArea> = React.memo(({ chil
|
||||
|
||||
{features.lists && <WrappedRoute path='/lists' layout={DefaultLayout} component={Lists} content={children} />}
|
||||
{features.lists && <WrappedRoute path='/list/:id' layout={DefaultLayout} component={ListTimeline} content={children} />}
|
||||
{features.circles && <WrappedRoute path='/circles/:id' layout={DefaultLayout} component={CircleTimeline} content={children} />}
|
||||
{features.circles && <WrappedRoute path='/circles' layout={DefaultLayout} component={Circles} content={children} />}
|
||||
{features.bookmarks && <WrappedRoute path='/bookmarks/all' layout={DefaultLayout} component={Bookmarks} content={children} />}
|
||||
{features.bookmarks && <WrappedRoute path='/bookmarks/:id' layout={DefaultLayout} component={Bookmarks} content={children} />}
|
||||
|
||||
@ -16,6 +16,7 @@ export const BubbleTimeline = lazy(() => import('pl-fe/pages/timelines/bubble-ti
|
||||
export const ChatIndex = lazy(() => import('pl-fe/pages/chats/chats'));
|
||||
export const Circle = lazy(() => import('pl-fe/pages/fun/circle'));
|
||||
export const Circles = lazy(() => import('pl-fe/pages/account-lists/circles'));
|
||||
export const CircleTimeline = lazy(() => import('pl-fe/pages/timelines/circle-timeline'));
|
||||
export const CommunityTimeline = lazy(() => import('pl-fe/pages/timelines/community-timeline'));
|
||||
export const ComposeEvent = lazy(() => import('pl-fe/pages/statuses/compose-event'));
|
||||
export const Conversations = lazy(() => import('pl-fe/pages/status-lists/conversations'));
|
||||
|
||||
@ -353,9 +353,14 @@
|
||||
"chats.main.blankslate.title": "No messages yet",
|
||||
"chats.main.blankslate_with_chats.subtitle": "Select from one of your open chats or create a new message.",
|
||||
"chats.main.blankslate_with_chats.title": "Select a chat",
|
||||
"circle.click_to_add": "Click here to add people",
|
||||
"circles.add_to_circle": "Add to circle",
|
||||
"circles.delete": "Delete circle",
|
||||
"circles.edit": "Edit circle",
|
||||
"circles.new.create": "Add circle",
|
||||
"circles.new.create_title": "Add circle",
|
||||
"circles.new.title_placeholder": "New circle title",
|
||||
"circles.remove_from_circle": "Remove from circle",
|
||||
"circles.subheading": "Your circles",
|
||||
"column.admin.account": "Moderate @{acct}",
|
||||
"column.admin.announcements": "Announcements",
|
||||
@ -616,6 +621,9 @@
|
||||
"confirmations.delete_bookmark_folder.confirm": "Delete folder",
|
||||
"confirmations.delete_bookmark_folder.heading": "Delete \"{name}\" folder?",
|
||||
"confirmations.delete_bookmark_folder.message": "Are you sure you want to delete the folder? The bookmarks will still be stored.",
|
||||
"confirmations.delete_circle.confirm": "Delete",
|
||||
"confirmations.delete_circle.heading": "Delete circle",
|
||||
"confirmations.delete_circle.message": "Are you sure you want to permanently delete this circle?",
|
||||
"confirmations.delete_event.confirm": "Delete",
|
||||
"confirmations.delete_event.heading": "Delete event",
|
||||
"confirmations.delete_event.message": "Are you sure you want to delete this event?",
|
||||
@ -788,6 +796,8 @@
|
||||
"empty_column.bookmarks": "You don't have any bookmarks yet. When you add one, it will show up here.",
|
||||
"empty_column.bookmarks.folder": "You don't have any bookmarks in this folder yet. When you add one, it will show up here.",
|
||||
"empty_column.bubble": "There is nothing here! Write something publicly to fill it up",
|
||||
"empty_column.circle": "There is nothing in this circle yet. When members of this circle create new posts, they will appear here.",
|
||||
"empty_column.circle_members": "There are no members in this circle. Use search to find users to add.",
|
||||
"empty_column.circles": "You don't have any circles yet. When you create one, it will show up here.",
|
||||
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
|
||||
"empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
|
||||
|
||||
82
packages/pl-fe/src/modals/circle-editor-modal.tsx
Normal file
82
packages/pl-fe/src/modals/circle-editor-modal.tsx
Normal file
@ -0,0 +1,82 @@
|
||||
import React, { useState } from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { CardHeader, CardTitle } from 'pl-fe/components/ui/card';
|
||||
import Modal from 'pl-fe/components/ui/modal';
|
||||
import Spinner from 'pl-fe/components/ui/spinner';
|
||||
import Stack from 'pl-fe/components/ui/stack';
|
||||
import Text from 'pl-fe/components/ui/text';
|
||||
import { useAddAccountsToCircle, useCircle, useCircleAccounts, useRemoveAccountsFromCircle } from 'pl-fe/queries/accounts/use-circles';
|
||||
import { useAccountSearch } from 'pl-fe/queries/search/use-search-accounts';
|
||||
|
||||
import Account from './list-editor-modal/components/account';
|
||||
import Search from './list-editor-modal/components/search';
|
||||
|
||||
import type { BaseModalProps } from 'pl-fe/features/ui/components/modal-root';
|
||||
|
||||
const messages = defineMessages({
|
||||
addToCircle: { id: 'circles.add_to_circle', defaultMessage: 'Add to circle' },
|
||||
removeFromCircle: { id: 'circles.remove_from_circle', defaultMessage: 'Remove from circle' },
|
||||
});
|
||||
|
||||
interface CircleEditorModalProps {
|
||||
circleId: string;
|
||||
}
|
||||
|
||||
const CircleEditorModal: React.FC<BaseModalProps & CircleEditorModalProps> = ({ circleId, onClose }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
|
||||
const { data: circle } = useCircle(circleId);
|
||||
const { data: accountIds = [] } = useCircleAccounts(circleId);
|
||||
const { data: searchAccountIds = [] } = useAccountSearch(searchValue, { following: true, limit: 5 });
|
||||
|
||||
const { mutate: addToCircle } = useAddAccountsToCircle(circleId);
|
||||
const { mutate: removeFromCircle } = useRemoveAccountsFromCircle(circleId);
|
||||
|
||||
const onAdd = (accountId: string) => addToCircle([accountId]);
|
||||
const onRemove = (accountId: string) => removeFromCircle([accountId]);
|
||||
|
||||
const onClickClose = () => {
|
||||
onClose('CIRCLE_EDITOR');
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={<FormattedMessage id='circles.edit' defaultMessage='Edit circle' />}
|
||||
onClose={onClickClose}
|
||||
>
|
||||
{circle ? (
|
||||
<Stack space={2}>
|
||||
{accountIds.length > 0 ? (
|
||||
<div>
|
||||
<CardHeader>
|
||||
<CardTitle title={intl.formatMessage(messages.removeFromCircle)} />
|
||||
</CardHeader>
|
||||
<div className='max-h-48 overflow-y-auto'>
|
||||
{accountIds.map(accountId => <Account key={accountId} accountId={accountId} added={accountIds.includes(accountId)} onAdd={onAdd} onRemove={onRemove} />)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Text theme='muted' size='sm'>
|
||||
<FormattedMessage id='empty_column.circle_members' defaultMessage='There are no members in this circle. Use search to find users to add.' />
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<CardHeader>
|
||||
<CardTitle title={intl.formatMessage(messages.addToCircle)} />
|
||||
</CardHeader>
|
||||
<Search value={searchValue} onSubmit={setSearchValue} />
|
||||
<div className='max-h-48 overflow-y-auto'>
|
||||
{searchAccountIds.map(accountId => <Account key={accountId} accountId={accountId} added={accountIds.includes(accountId)} onAdd={onAdd} onRemove={onRemove} />)}
|
||||
</div>
|
||||
</div>
|
||||
</Stack>
|
||||
) : <Spinner />}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export { CircleEditorModal as default, type CircleEditorModalProps };
|
||||
@ -4,7 +4,6 @@ import { defineMessages, useIntl } from 'react-intl';
|
||||
import IconButton from 'pl-fe/components/icon-button';
|
||||
import HStack from 'pl-fe/components/ui/hstack';
|
||||
import AccountContainer from 'pl-fe/containers/account-container';
|
||||
import { useAddAccountsToList, useRemoveAccountsFromList } from 'pl-fe/queries/accounts/use-lists';
|
||||
|
||||
const messages = defineMessages({
|
||||
remove: { id: 'lists.account.remove', defaultMessage: 'Remove from list' },
|
||||
@ -12,26 +11,21 @@ const messages = defineMessages({
|
||||
});
|
||||
|
||||
interface IAccount {
|
||||
listId: string;
|
||||
accountId: string;
|
||||
added?: boolean;
|
||||
onAdd: (accountId: string) => void;
|
||||
onRemove: (accountId: string) => void;
|
||||
}
|
||||
|
||||
const Account: React.FC<IAccount> = ({ listId, accountId, added }) => {
|
||||
const Account: React.FC<IAccount> = ({ accountId, added, onAdd, onRemove }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const { mutate: addToList } = useAddAccountsToList(listId);
|
||||
const { mutate: removeFromList } = useRemoveAccountsFromList(listId);
|
||||
|
||||
const onAdd = () => addToList([accountId]);
|
||||
const onRemove = () => removeFromList([accountId]);
|
||||
|
||||
let button;
|
||||
|
||||
if (added) {
|
||||
button = <IconButton src={require('@tabler/icons/outline/x.svg')} iconClassName='h-5 w-5' title={intl.formatMessage(messages.remove)} onClick={onRemove} />;
|
||||
button = <IconButton src={require('@tabler/icons/outline/x.svg')} iconClassName='h-5 w-5' title={intl.formatMessage(messages.remove)} onClick={() => onRemove(accountId)} />;
|
||||
} else {
|
||||
button = <IconButton src={require('@tabler/icons/outline/plus.svg')} iconClassName='h-5 w-5' title={intl.formatMessage(messages.add)} onClick={onAdd} />;
|
||||
button = <IconButton src={require('@tabler/icons/outline/plus.svg')} iconClassName='h-5 w-5' title={intl.formatMessage(messages.add)} onClick={() => onAdd(accountId)} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -37,7 +37,6 @@ const ListForm: React.FC<IListForm> = ({
|
||||
const { data: list } = useList(listId);
|
||||
const { mutate: updateList, isPending: disabled } = useUpdateList(listId!);
|
||||
|
||||
console.log(list);
|
||||
const [title, setTitle] = useState(list!.title);
|
||||
const [repliesPolicy, setRepliesPolicy] = useState(list!.replies_policy);
|
||||
const [exclusive, setExclusive] = useState(list!.exclusive);
|
||||
|
||||
@ -4,7 +4,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import { CardHeader, CardTitle } from 'pl-fe/components/ui/card';
|
||||
import Stack from 'pl-fe/components/ui/stack';
|
||||
import Text from 'pl-fe/components/ui/text';
|
||||
import { useListAccounts } from 'pl-fe/queries/accounts/use-lists';
|
||||
import { useAddAccountsToList, useListAccounts, useRemoveAccountsFromList } from 'pl-fe/queries/accounts/use-lists';
|
||||
import { useAccountSearch } from 'pl-fe/queries/search/use-search-accounts';
|
||||
|
||||
import Account from './account';
|
||||
@ -27,6 +27,12 @@ const ListMembersForm: React.FC<IListMembersForm> = ({ listId }) => {
|
||||
const { data: accountIds = [] } = useListAccounts(listId);
|
||||
const { data: searchAccountIds = [] } = useAccountSearch(searchValue, { following: true, limit: 5 });
|
||||
|
||||
const { mutate: addToList } = useAddAccountsToList(listId);
|
||||
const { mutate: removeFromList } = useRemoveAccountsFromList(listId);
|
||||
|
||||
const onAdd = (accountId: string) => addToList([accountId]);
|
||||
const onRemove = (accountId: string) => removeFromList([accountId]);
|
||||
|
||||
return (
|
||||
<Stack space={2}>
|
||||
{accountIds.length > 0 ? (
|
||||
@ -35,7 +41,7 @@ const ListMembersForm: React.FC<IListMembersForm> = ({ listId }) => {
|
||||
<CardTitle title={intl.formatMessage(messages.removeFromList)} />
|
||||
</CardHeader>
|
||||
<div className='max-h-48 overflow-y-auto'>
|
||||
{accountIds.map(accountId => <Account key={accountId} listId={listId} accountId={accountId} added={accountIds.includes(accountId)} />)}
|
||||
{accountIds.map(accountId => <Account key={accountId} accountId={accountId} added={accountIds.includes(accountId)} onAdd={onAdd} onRemove={onRemove} />)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
@ -50,7 +56,7 @@ const ListMembersForm: React.FC<IListMembersForm> = ({ listId }) => {
|
||||
</CardHeader>
|
||||
<Search value={searchValue} onSubmit={setSearchValue} />
|
||||
<div className='max-h-48 overflow-y-auto'>
|
||||
{searchAccountIds.map(accountId => <Account key={accountId} listId={listId} accountId={accountId} added={accountIds.includes(accountId)} />)}
|
||||
{searchAccountIds.map(accountId => <Account key={accountId} accountId={accountId} added={accountIds.includes(accountId)} onAdd={onAdd} onRemove={onRemove} />)}
|
||||
</div>
|
||||
</div>
|
||||
</Stack>
|
||||
|
||||
@ -97,7 +97,7 @@ const CirclesPage: React.FC = () => {
|
||||
{circles.map((circle) => (
|
||||
<ListItem
|
||||
key={circle.id}
|
||||
// to={`/circles/${circle.id}`}
|
||||
to={`/circles/${circle.id}`}
|
||||
label={
|
||||
<HStack alignItems='center' space={2}>
|
||||
<Icon src={require('@tabler/icons/outline/list.svg')} size={20} />
|
||||
|
||||
118
packages/pl-fe/src/pages/timelines/circle-timeline.tsx
Normal file
118
packages/pl-fe/src/pages/timelines/circle-timeline.tsx
Normal file
@ -0,0 +1,118 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { fetchCircleTimeline } from 'pl-fe/actions/timelines';
|
||||
import DropdownMenu from 'pl-fe/components/dropdown-menu';
|
||||
import MissingIndicator from 'pl-fe/components/missing-indicator';
|
||||
import Button from 'pl-fe/components/ui/button';
|
||||
import Column from 'pl-fe/components/ui/column';
|
||||
import Spinner from 'pl-fe/components/ui/spinner';
|
||||
import Timeline from 'pl-fe/features/ui/components/timeline';
|
||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||
import { useIsMobile } from 'pl-fe/hooks/use-is-mobile';
|
||||
import { useTheme } from 'pl-fe/hooks/use-theme';
|
||||
import { useCircle, useDeleteCircle } from 'pl-fe/queries/accounts/use-circles';
|
||||
import { useModalsStore } from 'pl-fe/stores/modals';
|
||||
|
||||
const messages = defineMessages({
|
||||
deleteHeading: { id: 'confirmations.delete_circle.heading', defaultMessage: 'Delete circle' },
|
||||
deleteMessage: { id: 'confirmations.delete_circle.message', defaultMessage: 'Are you sure you want to permanently delete this circle?' },
|
||||
deleteConfirm: { id: 'confirmations.delete_circle.confirm', defaultMessage: 'Delete' },
|
||||
editCircle: { id: 'circles.edit', defaultMessage: 'Edit circle' },
|
||||
deleteCircle: { id: 'circles.delete', defaultMessage: 'Delete circle' },
|
||||
});
|
||||
|
||||
const CircleTimelinePage: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const theme = useTheme();
|
||||
const isMobile = useIsMobile();
|
||||
const { openModal } = useModalsStore();
|
||||
|
||||
const { data: circle, isFetching } = useCircle(id);
|
||||
const { mutate: deleteCircle } = useDeleteCircle();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchCircleTimeline(id));
|
||||
}, [id]);
|
||||
|
||||
const handleLoadMore = () => {
|
||||
dispatch(fetchCircleTimeline(id, true));
|
||||
};
|
||||
|
||||
const handleEditClick = () => {
|
||||
openModal('CIRCLE_EDITOR', { circleId: id });
|
||||
};
|
||||
|
||||
const handleDeleteClick = (e: React.MouseEvent | React.KeyboardEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
openModal('CONFIRM', {
|
||||
heading: intl.formatMessage(messages.deleteHeading),
|
||||
message: intl.formatMessage(messages.deleteMessage),
|
||||
confirm: intl.formatMessage(messages.deleteConfirm),
|
||||
onConfirm: () => {
|
||||
deleteCircle(id);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const title = circle ? circle.title : id;
|
||||
|
||||
if (!circle && isFetching) {
|
||||
return (
|
||||
<Column>
|
||||
<div>
|
||||
<Spinner />
|
||||
</div>
|
||||
</Column>
|
||||
);
|
||||
} else if (!circle) {
|
||||
return (
|
||||
<MissingIndicator />
|
||||
);
|
||||
}
|
||||
|
||||
const emptyMessage = (
|
||||
<div>
|
||||
<FormattedMessage id='empty_column.circle' defaultMessage='There is nothing in this circle yet. When members of this circle create new posts, they will appear here.' />
|
||||
<br /><br />
|
||||
<Button onClick={handleEditClick}><FormattedMessage id='circle.click_to_add' defaultMessage='Click here to add people' /></Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
const items = [
|
||||
{
|
||||
text: intl.formatMessage(messages.editCircle),
|
||||
action: handleEditClick,
|
||||
icon: require('@tabler/icons/outline/edit.svg'),
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage(messages.deleteCircle),
|
||||
action: handleDeleteClick,
|
||||
icon: require('@tabler/icons/outline/trash.svg'),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Column
|
||||
label={title}
|
||||
action={<DropdownMenu items={items} src={require('@tabler/icons/outline/dots-vertical.svg')} />}
|
||||
transparent={!isMobile}
|
||||
>
|
||||
<Timeline
|
||||
className='black:p-0 black:sm:p-4 black:sm:pt-0'
|
||||
loadMoreClassName='black:sm:mx-4'
|
||||
scrollKey='circle_timeline'
|
||||
timelineId={`circle:${id}`}
|
||||
onLoadMore={handleLoadMore}
|
||||
emptyMessage={emptyMessage}
|
||||
divideType={(theme === 'black' || isMobile) ? 'border' : 'space'}
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
export { CircleTimelinePage as default };
|
||||
@ -1,11 +1,14 @@
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { type InfiniteData, useMutation, useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { useClient } from 'pl-fe/hooks/use-client';
|
||||
import { useFeatures } from 'pl-fe/hooks/use-features';
|
||||
|
||||
import { queryClient } from '../client';
|
||||
import { filterById } from '../utils/filter-id';
|
||||
import { makePaginatedResponseQuery } from '../utils/make-paginated-response-query';
|
||||
import { minifyAccountList } from '../utils/minify-list';
|
||||
|
||||
import type { Circle } from 'pl-api';
|
||||
import type { Circle, PaginatedResponse } from 'pl-api';
|
||||
|
||||
const useCircles = <T>(
|
||||
select?: ((data: Array<Circle>) => T),
|
||||
@ -58,4 +61,33 @@ const useUpdateCircle = (circleId: string) => {
|
||||
});
|
||||
};
|
||||
|
||||
export { useCircles, useCircle, useCreateCircle, useDeleteCircle, useUpdateCircle };
|
||||
const useCircleAccounts = makePaginatedResponseQuery(
|
||||
(circleId: string) => ['accountsLists', 'circles', circleId],
|
||||
(client, [circleId]) => client.circles.getCircleAccounts(circleId).then(minifyAccountList),
|
||||
);
|
||||
|
||||
const useAddAccountsToCircle = (circleId: string) => {
|
||||
const client = useClient();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ['accountsLists', 'circles', circleId, 'add'],
|
||||
mutationFn: (accountIds: Array<string>) => client.circles.addCircleAccounts(circleId, accountIds),
|
||||
onSettled: (_, __, accountIds) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['accountsLists', 'circles', circleId] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const useRemoveAccountsFromCircle = (circleId: string) => {
|
||||
const client = useClient();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ['accountsLists', 'circles', circleId, 'remove'],
|
||||
mutationFn: (accountIds: Array<string>) => client.circles.deleteCircleAccounts(circleId, accountIds),
|
||||
onSettled: (_, __, accountIds) => {
|
||||
queryClient.setQueryData<InfiniteData<PaginatedResponse<string>>>(['accountsLists', 'circles', circleId], filterById(accountIds));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export { useCircles, useCircle, useCreateCircle, useDeleteCircle, useUpdateCircle, useCircleAccounts, useAddAccountsToCircle, useRemoveAccountsFromCircle };
|
||||
|
||||
@ -7,6 +7,7 @@ import type { ICryptoAddress } from 'pl-fe/features/crypto-donate/components/cry
|
||||
import type { ModalType } from 'pl-fe/features/ui/components/modal-root';
|
||||
import type { AltTextModalProps } from 'pl-fe/modals/alt-text-modal';
|
||||
import type { BoostModalProps } from 'pl-fe/modals/boost-modal';
|
||||
import type { CircleEditorModalProps } from 'pl-fe/modals/circle-editor-modal';
|
||||
import type { CompareHistoryModalProps } from 'pl-fe/modals/compare-history-modal';
|
||||
import type { ComponentModalProps } from 'pl-fe/modals/component-modal';
|
||||
import type { ComposeInteractionPolicyModalProps } from 'pl-fe/modals/compose-interaction-policy-modal';
|
||||
@ -43,6 +44,7 @@ type OpenModalProps =
|
||||
| [type: 'ALT_TEXT', props: AltTextModalProps]
|
||||
| [type: 'BIRTHDAYS' | 'CREATE_GROUP' | 'HOTKEYS']
|
||||
| [type: 'BOOST', props: BoostModalProps]
|
||||
| [type: 'CIRCLE_EDITOR', props: CircleEditorModalProps]
|
||||
| [type: 'COMPARE_HISTORY', props: CompareHistoryModalProps]
|
||||
| [type: 'COMPONENT', props: ComponentModalProps]
|
||||
| [type: 'COMPOSE', props?: ComposeModalProps]
|
||||
|
||||
Reference in New Issue
Block a user