[Glitch] Mark pending accounts in collection editor
Port 41a3679d83f5f767c013dd4a29c153869469b03a to glitch-soc Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
parent
49f60c0b76
commit
26cee35f50
@ -116,7 +116,7 @@ const RevokeControls: React.FC<{
|
||||
);
|
||||
};
|
||||
|
||||
const PendingNote: React.FC = () => {
|
||||
export const PendingNote: React.FC = () => {
|
||||
return (
|
||||
<Callout
|
||||
variant='subtle'
|
||||
|
||||
@ -6,11 +6,11 @@ import { useHistory } from 'react-router-dom';
|
||||
|
||||
import type { Map as ImmutableMap } from 'immutable';
|
||||
|
||||
import { useComboboxItemProps } from '@/flavours/glitch/components/form_fields/combobox_field';
|
||||
import type { ApiMutedAccountJSON } from 'flavours/glitch/api_types/accounts';
|
||||
import type { ApiCollectionJSON } from 'flavours/glitch/api_types/collections';
|
||||
import { AccountListItem } from 'flavours/glitch/components/account_list_item';
|
||||
import { Avatar } from 'flavours/glitch/components/avatar';
|
||||
import { PendingBadge } from 'flavours/glitch/components/badge';
|
||||
import { Button } from 'flavours/glitch/components/button';
|
||||
import { DisplayName } from 'flavours/glitch/components/display_name';
|
||||
import { useAccountHandle } from 'flavours/glitch/components/display_name/default';
|
||||
@ -19,6 +19,7 @@ import {
|
||||
FormStack,
|
||||
ComboboxField,
|
||||
} from 'flavours/glitch/components/form_fields';
|
||||
import { useComboboxItemProps } from 'flavours/glitch/components/form_fields/combobox_field';
|
||||
import {
|
||||
ListItemContent,
|
||||
ListItemWrapper,
|
||||
@ -34,12 +35,14 @@ import { domain } from 'flavours/glitch/initial_state';
|
||||
import type { Relationship } from 'flavours/glitch/models/relationship';
|
||||
import {
|
||||
addCollectionItem,
|
||||
getCollectionItemIds,
|
||||
getEditorCollectionItems,
|
||||
removeCollectionItem,
|
||||
updateCollectionEditorField,
|
||||
} from 'flavours/glitch/reducers/slices/collections';
|
||||
import { useAppDispatch, useAppSelector } from 'flavours/glitch/store';
|
||||
|
||||
import { PendingNote } from '../detail';
|
||||
|
||||
import classes from './styles.module.scss';
|
||||
import { WizardStepTitle } from './wizard_step_title';
|
||||
|
||||
@ -47,8 +50,9 @@ const MAX_ACCOUNT_COUNT = 25;
|
||||
|
||||
const AddedAccountItem: React.FC<{
|
||||
accountId: string;
|
||||
pending?: boolean;
|
||||
onRemove: (id: string) => void;
|
||||
}> = ({ accountId, onRemove }) => {
|
||||
}> = ({ accountId, pending, onRemove }) => {
|
||||
const handleRemoveAccount = useCallback(() => {
|
||||
onRemove(accountId);
|
||||
}, [accountId, onRemove]);
|
||||
@ -65,7 +69,13 @@ const AddedAccountItem: React.FC<{
|
||||
[handleRemoveAccount],
|
||||
);
|
||||
|
||||
return <AccountListItem accountId={accountId} renderButton={renderButton} />;
|
||||
return (
|
||||
<AccountListItem
|
||||
accountId={accountId}
|
||||
badge={pending && <PendingBadge />}
|
||||
renderButton={renderButton}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const SuggestedAccountItem: React.FC<{ id: string }> = ({ id }) => {
|
||||
@ -186,22 +196,26 @@ export const CollectionAccounts: React.FC<{
|
||||
const { id, items: collectionItems } = collection ?? {};
|
||||
const isEditMode = !!id;
|
||||
|
||||
const addedAccountIds = useAppSelector(
|
||||
(state) => state.collections.editor.accountIds,
|
||||
const editorItemsFromState = useAppSelector(
|
||||
(state) => state.collections.editor.items,
|
||||
);
|
||||
|
||||
// In edit mode, we're bypassing state and just return collection items directly,
|
||||
// since they're edited "live", saving after each addition/deletion
|
||||
const accountIds = useMemo(
|
||||
// In edit mode, we're bypassing our Redux state and just work on the
|
||||
// collection items directly since they're edited "live", saving right
|
||||
// after each addition/deletion
|
||||
const editorItems = useMemo(
|
||||
() =>
|
||||
isEditMode ? getCollectionItemIds(collectionItems) : addedAccountIds,
|
||||
[isEditMode, collectionItems, addedAccountIds],
|
||||
isEditMode
|
||||
? getEditorCollectionItems(collectionItems)
|
||||
: editorItemsFromState,
|
||||
[isEditMode, collectionItems, editorItemsFromState],
|
||||
);
|
||||
const hasPendingItems = editorItems.some((item) => item.state === 'pending');
|
||||
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
|
||||
const hasAccounts = accountIds.length > 0;
|
||||
const hasMaxAccounts = accountIds.length === MAX_ACCOUNT_COUNT;
|
||||
const hasItems = editorItems.length > 0;
|
||||
const hasMaxItems = editorItems.length === MAX_ACCOUNT_COUNT;
|
||||
|
||||
const {
|
||||
accounts: suggestedAccounts,
|
||||
@ -211,7 +225,8 @@ export const CollectionAccounts: React.FC<{
|
||||
} = useSearchAccounts({
|
||||
withRelationships: true,
|
||||
// Don't suggest accounts that were already added
|
||||
filterResults: (account) => !accountIds.includes(account.id),
|
||||
filterResults: (account) =>
|
||||
!editorItems.find((item) => item.account_id === account.id),
|
||||
});
|
||||
|
||||
const relationships = useAppSelector((state) => state.relationships);
|
||||
@ -239,24 +254,33 @@ export const CollectionAccounts: React.FC<{
|
||||
(accountId: string) => {
|
||||
dispatch(
|
||||
updateCollectionEditorField({
|
||||
field: 'accountIds',
|
||||
value: accountIds.filter((id) => id !== accountId),
|
||||
field: 'items',
|
||||
value: editorItems.filter((item) => item.account_id !== accountId),
|
||||
}),
|
||||
);
|
||||
},
|
||||
[accountIds, dispatch],
|
||||
[editorItems, dispatch],
|
||||
);
|
||||
|
||||
const addAccountItem = useCallback(
|
||||
(item: ApiMutedAccountJSON) => {
|
||||
dispatch(
|
||||
updateCollectionEditorField({
|
||||
field: 'accountIds',
|
||||
value: [...accountIds, item.id],
|
||||
field: 'items',
|
||||
value: [
|
||||
...editorItems,
|
||||
{
|
||||
account_id: item.id,
|
||||
state:
|
||||
item.feature_approval.current_user === 'manual'
|
||||
? 'pending'
|
||||
: 'accepted',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
},
|
||||
[accountIds, dispatch],
|
||||
[editorItems, dispatch],
|
||||
);
|
||||
|
||||
const instantRemoveAccountItem = useCallback(
|
||||
@ -322,12 +346,10 @@ export const CollectionAccounts: React.FC<{
|
||||
e.preventDefault();
|
||||
|
||||
if (!id) {
|
||||
history.push(`/collections/new/details`, {
|
||||
account_ids: accountIds,
|
||||
});
|
||||
history.push('/collections/new/details');
|
||||
}
|
||||
},
|
||||
[id, history, accountIds],
|
||||
[id, history],
|
||||
);
|
||||
|
||||
const inputId = useId();
|
||||
@ -348,16 +370,17 @@ export const CollectionAccounts: React.FC<{
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{hasPendingItems && <PendingNote />}
|
||||
<ComboboxField
|
||||
id={inputId}
|
||||
label={intl.formatMessage({
|
||||
id: 'collections.search_accounts_label',
|
||||
defaultMessage: 'Search for an account to add',
|
||||
})}
|
||||
value={hasMaxAccounts ? '' : searchValue}
|
||||
value={hasMaxItems ? '' : searchValue}
|
||||
onChange={handleSearchValueChange}
|
||||
onKeyDown={handleSearchKeyDown}
|
||||
disabled={hasMaxAccounts}
|
||||
disabled={hasMaxItems}
|
||||
isLoading={isLoadingSuggestions}
|
||||
items={groupedItems}
|
||||
getItemId={getItemId}
|
||||
@ -366,7 +389,7 @@ export const CollectionAccounts: React.FC<{
|
||||
renderGroupTitle={renderGroupTitle}
|
||||
onSelectItem={handleSelectItem}
|
||||
status={
|
||||
hasMaxAccounts
|
||||
hasMaxItems
|
||||
? {
|
||||
variant: 'warning',
|
||||
message: intl.formatMessage({
|
||||
@ -381,12 +404,12 @@ export const CollectionAccounts: React.FC<{
|
||||
</header>
|
||||
|
||||
<div>
|
||||
{hasAccounts && (
|
||||
{hasItems && (
|
||||
<AccountsHeadingElement className={classes.listHeading}>
|
||||
<FormattedMessage
|
||||
id='collections.hints.accounts_counter'
|
||||
defaultMessage='{count}/{max} accounts'
|
||||
values={{ count: accountIds.length, max: MAX_ACCOUNT_COUNT }}
|
||||
values={{ count: editorItems.length, max: MAX_ACCOUNT_COUNT }}
|
||||
/>
|
||||
</AccountsHeadingElement>
|
||||
)}
|
||||
@ -413,14 +436,15 @@ export const CollectionAccounts: React.FC<{
|
||||
/>
|
||||
}
|
||||
>
|
||||
{accountIds.map((accountId, index) => (
|
||||
{editorItems.map(({ account_id, state }, index) => (
|
||||
<Article
|
||||
key={accountId}
|
||||
key={account_id}
|
||||
aria-posinset={index}
|
||||
aria-setsize={accountIds.length}
|
||||
aria-setsize={editorItems.length}
|
||||
>
|
||||
<AddedAccountItem
|
||||
accountId={accountId}
|
||||
accountId={account_id}
|
||||
pending={state === 'pending'}
|
||||
onRemove={handleRemoveAccountItem}
|
||||
/>
|
||||
</Article>
|
||||
@ -429,7 +453,7 @@ export const CollectionAccounts: React.FC<{
|
||||
</Scrollable>
|
||||
</div>
|
||||
</FormStack>
|
||||
{!isEditMode && hasAccounts && (
|
||||
{!isEditMode && hasItems && (
|
||||
<div className={classes.stickyFooter}>
|
||||
<Button type='submit'>
|
||||
{id ? (
|
||||
|
||||
@ -46,7 +46,7 @@ import { WizardStepTitle } from './wizard_step_title';
|
||||
export const CollectionDetails: React.FC = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const history = useHistory();
|
||||
const { id, name, description, topic, discoverable, sensitive, accountIds } =
|
||||
const { id, name, description, topic, discoverable, sensitive, items } =
|
||||
useAppSelector((state) => state.collections.editor);
|
||||
|
||||
const handleNameChange = useCallback(
|
||||
@ -123,7 +123,7 @@ export const CollectionDetails: React.FC = () => {
|
||||
description,
|
||||
discoverable,
|
||||
sensitive,
|
||||
account_ids: accountIds,
|
||||
account_ids: items.map((item) => item.account_id),
|
||||
};
|
||||
if (topic) {
|
||||
payload.tag_name = topic;
|
||||
@ -152,7 +152,7 @@ export const CollectionDetails: React.FC = () => {
|
||||
sensitive,
|
||||
dispatch,
|
||||
history,
|
||||
accountIds,
|
||||
items,
|
||||
currentUserName,
|
||||
],
|
||||
);
|
||||
|
||||
@ -16,6 +16,7 @@ import type {
|
||||
ApiCollectionJSON,
|
||||
ApiCreateCollectionPayload,
|
||||
ApiUpdateCollectionPayload,
|
||||
CollectionAccountItem,
|
||||
} from '@/flavours/glitch/api_types/collections';
|
||||
import { me } from '@/flavours/glitch/initial_state';
|
||||
import {
|
||||
@ -41,6 +42,16 @@ interface CollectionState {
|
||||
editor: EditorState;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a subset of the `CollectionAccountItem` type
|
||||
* for use in the editor. Here, `account_id` is always defined
|
||||
* and `state` is more limited.
|
||||
*/
|
||||
export interface EditorCollectionItem {
|
||||
account_id: string;
|
||||
state: 'pending' | 'accepted';
|
||||
}
|
||||
|
||||
interface EditorState {
|
||||
id: string | null;
|
||||
name: string;
|
||||
@ -49,7 +60,7 @@ interface EditorState {
|
||||
language: string | null;
|
||||
discoverable: boolean;
|
||||
sensitive: boolean;
|
||||
accountIds: string[];
|
||||
items: EditorCollectionItem[];
|
||||
}
|
||||
|
||||
interface UpdateEditorFieldPayload<K extends keyof EditorState> {
|
||||
@ -68,7 +79,7 @@ const initialState: CollectionState = {
|
||||
language: null,
|
||||
discoverable: true,
|
||||
sensitive: false,
|
||||
accountIds: [],
|
||||
items: [],
|
||||
},
|
||||
};
|
||||
|
||||
@ -87,7 +98,7 @@ const collectionSlice = createSlice({
|
||||
language: collection?.language ?? '',
|
||||
discoverable: collection?.discoverable ?? true,
|
||||
sensitive: collection?.sensitive ?? false,
|
||||
accountIds: getCollectionItemIds(collection?.items ?? []),
|
||||
items: getEditorCollectionItems(collection?.items ?? []),
|
||||
};
|
||||
},
|
||||
reset(state) {
|
||||
@ -338,7 +349,12 @@ export const selectAccountCollections = createAppSelector(
|
||||
},
|
||||
);
|
||||
|
||||
const onlyExistingIds = (id?: string): id is string => !!id;
|
||||
const isEditorItem = (
|
||||
item: Partial<CollectionAccountItem>,
|
||||
): item is EditorCollectionItem =>
|
||||
!!item.account_id && (item.state === 'accepted' || item.state === 'pending');
|
||||
|
||||
export const getCollectionItemIds = (items?: ApiCollectionJSON['items']) =>
|
||||
items?.map((item) => item.account_id).filter(onlyExistingIds) ?? [];
|
||||
export const getEditorCollectionItems = (items?: CollectionAccountItem[]) =>
|
||||
items
|
||||
?.map(({ account_id, state }) => ({ account_id, state }))
|
||||
.filter(isEditorItem) ?? [];
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user