Migrate to external library for interacting with API
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
@@ -91,7 +91,6 @@ const Announcements: React.FC = () => {
|
||||
|
||||
const { data: announcements, isLoading } = useAnnouncements();
|
||||
|
||||
|
||||
const handleCreateAnnouncement = () => {
|
||||
dispatch(openModal('EDIT_ANNOUNCEMENT'));
|
||||
};
|
||||
|
||||
@@ -9,7 +9,6 @@ import { useFeatures } from 'soapbox/hooks';
|
||||
|
||||
import NewFolderForm from './components/new-folder-form';
|
||||
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' },
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Button, Combobox, ComboboxInput, ComboboxList, ComboboxOption, Combobox
|
||||
import { useChatContext } from 'soapbox/contexts/chat-context';
|
||||
import UploadButton from 'soapbox/features/compose/components/upload-button';
|
||||
import emojiSearch from 'soapbox/features/emoji/search';
|
||||
import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
|
||||
import { useAppDispatch, useAppSelector, useInstance } from 'soapbox/hooks';
|
||||
import { Attachment } from 'soapbox/types/entities';
|
||||
import { textAtCursorMatchesToken } from 'soapbox/utils/suggestions';
|
||||
|
||||
@@ -45,9 +45,9 @@ interface IChatComposer extends Pick<React.TextareaHTMLAttributes<HTMLTextAreaEl
|
||||
onSelectFile: (files: FileList, intl: IntlShape) => void;
|
||||
resetFileKey: number | null;
|
||||
resetContentKey: number | null;
|
||||
attachments?: Attachment[];
|
||||
onDeleteAttachment?: (i: number) => void;
|
||||
uploadCount?: number;
|
||||
attachment?: Attachment | null;
|
||||
onDeleteAttachment?: () => void;
|
||||
uploading?: boolean;
|
||||
uploadProgress?: number;
|
||||
}
|
||||
|
||||
@@ -63,29 +63,25 @@ const ChatComposer = React.forwardRef<HTMLTextAreaElement | null, IChatComposer>
|
||||
resetFileKey,
|
||||
resetContentKey,
|
||||
onPaste,
|
||||
attachments = [],
|
||||
attachment,
|
||||
onDeleteAttachment,
|
||||
uploadCount = 0,
|
||||
uploading,
|
||||
uploadProgress,
|
||||
}, ref) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const features = useFeatures();
|
||||
|
||||
const { chat } = useChatContext();
|
||||
|
||||
const isBlocked = useAppSelector((state) => state.getIn(['relationships', chat?.account?.id, 'blocked_by']));
|
||||
const isBlocking = useAppSelector((state) => state.getIn(['relationships', chat?.account?.id, 'blocking']));
|
||||
const maxCharacterCount = useAppSelector((state) => state.instance.configuration.chats.max_characters);
|
||||
const attachmentLimit = useAppSelector(state => state.instance.configuration.chats.max_media_attachments);
|
||||
const maxCharacterCount = useInstance().configuration.chats.max_characters;
|
||||
|
||||
const [suggestions, setSuggestions] = useState<Suggestion>(initialSuggestionState);
|
||||
const isSuggestionsAvailable = suggestions.list.length > 0;
|
||||
|
||||
const isUploading = uploadCount > 0;
|
||||
const hasAttachment = attachments.length > 0;
|
||||
const isOverCharacterLimit = maxCharacterCount && value?.length > maxCharacterCount;
|
||||
const isSubmitDisabled = disabled || isUploading || isOverCharacterLimit || (value.length === 0 && !hasAttachment);
|
||||
const isSubmitDisabled = disabled || uploading || isOverCharacterLimit || (value.length === 0 && !attachment);
|
||||
|
||||
const overLimitText = maxCharacterCount ? maxCharacterCount - value?.length : '';
|
||||
|
||||
@@ -170,17 +166,15 @@ const ChatComposer = React.forwardRef<HTMLTextAreaElement | null, IChatComposer>
|
||||
<div className='h-5' />
|
||||
|
||||
<HStack alignItems='stretch' justifyContent='between' space={4}>
|
||||
{features.chatsMedia && (
|
||||
<Stack justifyContent='end' alignItems='center' className='mb-1.5 w-10'>
|
||||
<UploadButton
|
||||
onSelectFile={onSelectFile}
|
||||
resetFileKey={resetFileKey}
|
||||
iconClassName='h-5 w-5'
|
||||
className='text-primary-500'
|
||||
disabled={isUploading || (attachments.length >= attachmentLimit)}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
<Stack justifyContent='end' alignItems='center' className='mb-1.5 w-10'>
|
||||
<UploadButton
|
||||
onSelectFile={onSelectFile}
|
||||
resetFileKey={resetFileKey}
|
||||
iconClassName='h-5 w-5'
|
||||
className='text-primary-500'
|
||||
disabled={uploading || !!attachment}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<Stack grow>
|
||||
<Combobox onSelect={onSelectComboboxOption}>
|
||||
@@ -198,9 +192,9 @@ const ChatComposer = React.forwardRef<HTMLTextAreaElement | null, IChatComposer>
|
||||
autoGrow
|
||||
maxRows={5}
|
||||
disabled={disabled}
|
||||
attachments={attachments}
|
||||
attachment={attachment}
|
||||
onDeleteAttachment={onDeleteAttachment}
|
||||
uploadCount={uploadCount}
|
||||
uploading={uploading}
|
||||
uploadProgress={uploadProgress}
|
||||
/>
|
||||
{isSuggestionsAvailable ? (
|
||||
|
||||
@@ -7,30 +7,21 @@ import ChatPendingUpload from './chat-pending-upload';
|
||||
import ChatUpload from './chat-upload';
|
||||
|
||||
interface IChatTextarea extends React.ComponentProps<typeof Textarea> {
|
||||
attachments?: Attachment[];
|
||||
onDeleteAttachment?: (i: number) => void;
|
||||
uploadCount?: number;
|
||||
attachment?: Attachment | null;
|
||||
onDeleteAttachment?: () => void;
|
||||
uploading?: boolean;
|
||||
uploadProgress?: number;
|
||||
}
|
||||
|
||||
/** Custom textarea for chats. */
|
||||
const ChatTextarea: React.FC<IChatTextarea> = React.forwardRef(({
|
||||
attachments,
|
||||
attachment,
|
||||
onDeleteAttachment,
|
||||
uploadCount = 0,
|
||||
uploading,
|
||||
uploadProgress = 0,
|
||||
...rest
|
||||
}, ref) => {
|
||||
const isUploading = uploadCount > 0;
|
||||
|
||||
const handleDeleteAttachment = (i: number) => () => {
|
||||
if (onDeleteAttachment) {
|
||||
onDeleteAttachment(i);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`
|
||||
}, ref) => (
|
||||
<div className={`
|
||||
block
|
||||
w-full
|
||||
rounded-md border border-gray-400
|
||||
@@ -41,30 +32,29 @@ const ChatTextarea: React.FC<IChatTextarea> = React.forwardRef(({
|
||||
dark:bg-gray-800 dark:text-gray-100 dark:ring-1 dark:ring-gray-800 dark:placeholder:text-gray-600
|
||||
dark:focus-within:border-primary-500 dark:focus-within:ring-primary-500
|
||||
`}
|
||||
>
|
||||
{(!!attachments?.length || isUploading) && (
|
||||
<HStack className='-ml-2 -mt-2 p-3 pb-0' wrap>
|
||||
{attachments?.map((attachment, i) => (
|
||||
<div className='ml-2 mt-2 flex'>
|
||||
<ChatUpload
|
||||
key={attachment.id}
|
||||
attachment={attachment}
|
||||
onDelete={handleDeleteAttachment(i)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
>
|
||||
{(attachment || uploading) && (
|
||||
<HStack className='-ml-2 -mt-2 p-3 pb-0' wrap>
|
||||
{attachment && (
|
||||
<div className='ml-2 mt-2 flex'>
|
||||
<ChatUpload
|
||||
key={attachment.id}
|
||||
attachment={attachment}
|
||||
onDelete={onDeleteAttachment}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{Array.from(Array(uploadCount)).map(() => (
|
||||
<div className='ml-2 mt-2 flex'>
|
||||
<ChatPendingUpload progress={uploadProgress} />
|
||||
</div>
|
||||
))}
|
||||
</HStack>
|
||||
)}
|
||||
{uploading && (
|
||||
<div className='ml-2 mt-2 flex'>
|
||||
<ChatPendingUpload progress={uploadProgress} />
|
||||
</div>
|
||||
)}
|
||||
</HStack>
|
||||
)}
|
||||
|
||||
<Textarea ref={ref} theme='transparent' {...rest} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
<Textarea ref={ref} theme='transparent' {...rest} />
|
||||
</div>
|
||||
));
|
||||
|
||||
export { ChatTextarea as default };
|
||||
|
||||
@@ -4,7 +4,7 @@ import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { uploadMedia } from 'soapbox/actions/media';
|
||||
import { Stack } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
import { useAppDispatch } from 'soapbox/hooks';
|
||||
import { normalizeAttachment } from 'soapbox/normalizers';
|
||||
import { IChat, useChatActions } from 'soapbox/queries/chats';
|
||||
import toast from 'soapbox/toast';
|
||||
@@ -53,20 +53,19 @@ const Chat: React.FC<ChatInterface> = ({ chat, inputRef, className }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { createChatMessage } = useChatActions(chat.id);
|
||||
const attachmentLimit = useAppSelector(state => state.instance.configuration.chats.max_media_attachments);
|
||||
|
||||
const [content, setContent] = useState<string>('');
|
||||
const [attachments, setAttachments] = useState<Attachment[]>([]);
|
||||
const [uploadCount, setUploadCount] = useState(0);
|
||||
const [attachment, setAttachment] = useState<Attachment | null>(null);
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [uploadProgress, setUploadProgress] = useState(0);
|
||||
const [resetContentKey, setResetContentKey] = useState<number>(fileKeyGen());
|
||||
const [resetFileKey, setResetFileKey] = useState<number>(fileKeyGen());
|
||||
const [errorMessage, setErrorMessage] = useState<string>();
|
||||
|
||||
const isSubmitDisabled = content.length === 0 && attachments.length === 0;
|
||||
const isSubmitDisabled = content.length === 0 && !attachment;
|
||||
|
||||
const submitMessage = () => {
|
||||
createChatMessage.mutate({ chatId: chat.id, content, mediaIds: attachments.map(a => a.id) }, {
|
||||
createChatMessage.mutate({ chatId: chat.id, content, mediaId: attachment?.id }, {
|
||||
onSuccess: () => {
|
||||
setErrorMessage(undefined);
|
||||
},
|
||||
@@ -85,8 +84,8 @@ const Chat: React.FC<ChatInterface> = ({ chat, inputRef, className }) => {
|
||||
clearNativeInputValue(inputRef.current);
|
||||
}
|
||||
setContent('');
|
||||
setAttachments([]);
|
||||
setUploadCount(0);
|
||||
setAttachment(null);
|
||||
setUploading(false);
|
||||
setUploadProgress(0);
|
||||
setResetFileKey(fileKeyGen());
|
||||
setResetContentKey(fileKeyGen());
|
||||
@@ -129,10 +128,8 @@ const Chat: React.FC<ChatInterface> = ({ chat, inputRef, className }) => {
|
||||
|
||||
const handleMouseOver = () => markRead();
|
||||
|
||||
const handleRemoveFile = (i: number) => {
|
||||
const newAttachments = [...attachments];
|
||||
newAttachments.splice(i, 1);
|
||||
setAttachments(newAttachments);
|
||||
const handleRemoveFile = () => {
|
||||
setAttachment(null);
|
||||
setResetFileKey(fileKeyGen());
|
||||
};
|
||||
|
||||
@@ -142,26 +139,20 @@ const Chat: React.FC<ChatInterface> = ({ chat, inputRef, className }) => {
|
||||
};
|
||||
|
||||
const handleFiles = (files: FileList) => {
|
||||
if (files.length + attachments.length > attachmentLimit) {
|
||||
if (attachment) {
|
||||
toast.error(messages.uploadErrorLimit);
|
||||
return;
|
||||
}
|
||||
|
||||
setUploadCount(files.length);
|
||||
setUploading(true);
|
||||
|
||||
const promises = Array.from(files).map(async(file) => {
|
||||
const data = new FormData();
|
||||
data.append('file', file);
|
||||
const response = await dispatch(uploadMedia(data, onUploadProgress));
|
||||
return normalizeAttachment(response.json);
|
||||
});
|
||||
dispatch(uploadMedia({ file: files[0] }, onUploadProgress)).then(response => {
|
||||
const newAttachment = normalizeAttachment(response);
|
||||
|
||||
return Promise.all(promises)
|
||||
.then((newAttachments) => {
|
||||
setAttachments([...attachments, ...newAttachments]);
|
||||
setUploadCount(0);
|
||||
})
|
||||
.catch(() => setUploadCount(0));
|
||||
setAttachment(newAttachment);
|
||||
setUploading(false);
|
||||
})
|
||||
.catch(() => setUploading(false));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -187,9 +178,9 @@ const Chat: React.FC<ChatInterface> = ({ chat, inputRef, className }) => {
|
||||
resetFileKey={resetFileKey}
|
||||
resetContentKey={resetContentKey}
|
||||
onPaste={handlePaste}
|
||||
attachments={attachments}
|
||||
attachment={attachment}
|
||||
onDeleteAttachment={handleRemoveFile}
|
||||
uploadCount={uploadCount}
|
||||
uploading={uploading}
|
||||
uploadProgress={uploadProgress}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
@@ -51,7 +51,6 @@ const LanguageDropdown: React.FC<ILanguageDropdown> = ({ composeId }) => {
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
|
||||
|
||||
const { x, y, strategy, refs, middlewareData, placement } = useFloating<HTMLButtonElement>({
|
||||
placement: 'top',
|
||||
middleware: [
|
||||
|
||||
@@ -135,7 +135,6 @@ const PrivacyDropdownMenu: React.FC<IPrivacyDropdownMenu> = ({
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
<Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
|
||||
{({ opacity, scaleX, scaleY }) => (
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { directComposeById } from 'soapbox/actions/compose';
|
||||
import { expandDirectTimeline } from 'soapbox/actions/timelines';
|
||||
import { useDirectStream } from 'soapbox/api/hooks';
|
||||
import AccountSearch from 'soapbox/components/account-search';
|
||||
import { Column } from 'soapbox/components/ui';
|
||||
import { useAppSelector, useAppDispatch } from 'soapbox/hooks';
|
||||
|
||||
import Timeline from '../ui/components/timeline';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.direct', defaultMessage: 'Direct messages' },
|
||||
searchPlaceholder: { id: 'direct.search_placeholder', defaultMessage: 'Send a message to…' },
|
||||
});
|
||||
|
||||
const DirectTimeline = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const next = useAppSelector(state => state.timelines.get('direct')?.next);
|
||||
|
||||
useDirectStream();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(expandDirectTimeline({}, intl));
|
||||
}, []);
|
||||
|
||||
const handleSuggestion = (accountId: string) => {
|
||||
dispatch(directComposeById(accountId));
|
||||
};
|
||||
|
||||
const handleLoadMore = (maxId: string) => {
|
||||
dispatch(expandDirectTimeline({ url: next, maxId }, intl));
|
||||
};
|
||||
|
||||
return (
|
||||
<Column label={intl.formatMessage(messages.heading)}>
|
||||
<AccountSearch
|
||||
placeholder={intl.formatMessage(messages.searchPlaceholder)}
|
||||
onSelected={handleSuggestion}
|
||||
/>
|
||||
|
||||
<Timeline
|
||||
scrollKey='direct_timeline'
|
||||
timelineId='direct'
|
||||
onLoadMore={handleLoadMore}
|
||||
emptyMessage={<FormattedMessage id='empty_column.direct' defaultMessage="You don't have any direct messages yet. When you send or receive one, it will show up here." />}
|
||||
divideType='border'
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
export { DirectTimeline as default };
|
||||
@@ -11,7 +11,7 @@ interface ICSVExporter {
|
||||
input_hint: MessageDescriptor;
|
||||
submit: MessageDescriptor;
|
||||
};
|
||||
action: () => (dispatch: AppDispatch, getState: () => RootState) => Promise<void>;
|
||||
action: () => (dispatch: AppDispatch, getState: () => RootState) => Promise<any>;
|
||||
}
|
||||
|
||||
const CSVExporter: React.FC<ICSVExporter> = ({ messages, action }) => {
|
||||
|
||||
@@ -91,7 +91,7 @@ const EditFilter: React.FC<IEditFilter> = ({ params }) => {
|
||||
const [notFound, setNotFound] = useState(false);
|
||||
|
||||
const [title, setTitle] = useState('');
|
||||
const [expiresIn, setExpiresIn] = useState<string | null>(null);
|
||||
const [expiresIn, setExpiresIn] = useState<number | undefined>();
|
||||
const [homeTimeline, setHomeTimeline] = useState(true);
|
||||
const [publicTimeline, setPublicTimeline] = useState(false);
|
||||
const [notifications, setNotifications] = useState(false);
|
||||
@@ -111,7 +111,7 @@ const EditFilter: React.FC<IEditFilter> = ({ params }) => {
|
||||
}), []);
|
||||
|
||||
const handleSelectChange: React.ChangeEventHandler<HTMLSelectElement> = e => {
|
||||
setExpiresIn(e.target.value);
|
||||
setExpiresIn(+e.target.value || undefined);
|
||||
};
|
||||
|
||||
const handleAddNew: React.FormEventHandler = e => {
|
||||
@@ -189,15 +189,13 @@ const EditFilter: React.FC<IEditFilter> = ({ params }) => {
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{features.filtersExpiration && (
|
||||
<FormGroup labelText={intl.formatMessage(messages.expires)}>
|
||||
<SelectDropdown
|
||||
items={expirations}
|
||||
defaultValue=''
|
||||
onChange={handleSelectChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
<FormGroup labelText={intl.formatMessage(messages.expires)}>
|
||||
<SelectDropdown
|
||||
items={expirations}
|
||||
defaultValue=''
|
||||
onChange={handleSelectChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<Stack>
|
||||
<Text size='sm' weight='medium'>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import debounce from 'lodash/debounce';
|
||||
import React, { useEffect } from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
@@ -17,19 +16,10 @@ const FollowRecommendations: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
const suggestions = useAppSelector((state) => state.suggestions.items);
|
||||
const hasMore = useAppSelector((state) => !!state.suggestions.next);
|
||||
const isLoading = useAppSelector((state) => state.suggestions.isLoading);
|
||||
|
||||
const handleLoadMore = debounce(() => {
|
||||
if (isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return dispatch(fetchSuggestions({ limit: 20 }));
|
||||
}, 300);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchSuggestions({ limit: 20 }));
|
||||
dispatch(fetchSuggestions(20));
|
||||
}, []);
|
||||
|
||||
if (suggestions.size === 0 && !isLoading) {
|
||||
@@ -48,8 +38,6 @@ const FollowRecommendations: React.FC = () => {
|
||||
<ScrollableList
|
||||
isLoading={isLoading}
|
||||
scrollKey='suggestions'
|
||||
onLoadMore={handleLoadMore}
|
||||
hasMore={hasMore}
|
||||
itemClassName='pb-4'
|
||||
>
|
||||
{suggestions.map((suggestion) => (
|
||||
|
||||
@@ -10,7 +10,6 @@ const messages = defineMessages({
|
||||
heading: { id: 'column.mutes', defaultMessage: 'Mutes' },
|
||||
});
|
||||
|
||||
|
||||
const Mutes: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ const NotificationFilterBar = () => {
|
||||
action: onClick('mention'),
|
||||
name: 'mention',
|
||||
});
|
||||
if (features.accountNotifies || features.accountSubscriptions) items.push({
|
||||
if (features.accountNotifies) items.push({
|
||||
text: <Icon className='h-4 w-4' src={require('@tabler/icons/outline/bell-ringing.svg')} />,
|
||||
title: intl.formatMessage(messages.statuses),
|
||||
action: onClick('status'),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import debounce from 'lodash/debounce';
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
@@ -9,15 +8,7 @@ import AccountContainer from 'soapbox/containers/account-container';
|
||||
import { useOnboardingSuggestions } from 'soapbox/queries/suggestions';
|
||||
|
||||
const SuggestedAccountsStep = ({ onNext }: { onNext: () => void }) => {
|
||||
const { data, fetchNextPage, hasNextPage, isFetching } = useOnboardingSuggestions();
|
||||
|
||||
const handleLoadMore = debounce(() => {
|
||||
if (isFetching) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fetchNextPage();
|
||||
}, 300);
|
||||
const { data, isFetching } = useOnboardingSuggestions();
|
||||
|
||||
const renderSuggestions = () => {
|
||||
if (!data) {
|
||||
@@ -29,8 +20,6 @@ const SuggestedAccountsStep = ({ onNext }: { onNext: () => void }) => {
|
||||
<ScrollableList
|
||||
isLoading={isFetching}
|
||||
scrollKey='suggestions'
|
||||
onLoadMore={handleLoadMore}
|
||||
hasMore={hasNextPage}
|
||||
useWindowScroll={false}
|
||||
style={{ height: 320 }}
|
||||
>
|
||||
|
||||
@@ -119,9 +119,7 @@ const SoapboxConfig: React.FC = () => {
|
||||
const file = e.target.files?.item(0);
|
||||
|
||||
if (file) {
|
||||
data.append('file', file);
|
||||
|
||||
dispatch(uploadMedia(data)).then(({ data }: any) => {
|
||||
dispatch(uploadMedia({ file})).then((data: any) => {
|
||||
handleChange(path, () => data.url)(e);
|
||||
}).catch(console.error);
|
||||
}
|
||||
|
||||
@@ -54,7 +54,6 @@ const DetailsStep: React.FC<IDetailsStep> = ({ params, onChange }) => {
|
||||
|
||||
const handleImageClear = (property: keyof CreateGroupParams) => () => onChange({ [property]: undefined });
|
||||
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<div className='relative mb-12 flex'>
|
||||
|
||||
@@ -14,7 +14,6 @@ import { isStandalone } from 'soapbox/utils/state';
|
||||
|
||||
import type { PlfeResponse } from 'soapbox/api';
|
||||
|
||||
|
||||
const SignUpPanel = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const instance = useInstance();
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import React from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import {
|
||||
subscribeAccount,
|
||||
unsubscribeAccount,
|
||||
} from 'soapbox/actions/accounts';
|
||||
import { useFollow } from 'soapbox/api/hooks';
|
||||
import { IconButton } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useFeatures } from 'soapbox/hooks';
|
||||
import { useFeatures } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
import type { Account as AccountEntity } from 'soapbox/types/entities';
|
||||
@@ -26,7 +22,6 @@ interface ISubscriptionButton {
|
||||
}
|
||||
|
||||
const SubscriptionButton = ({ account }: ISubscriptionButton) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const features = useFeatures();
|
||||
const intl = useIntl();
|
||||
const { follow } = useFollow();
|
||||
@@ -40,51 +35,23 @@ const SubscriptionButton = ({ account }: ISubscriptionButton) => {
|
||||
? intl.formatMessage(messages.unsubscribe, { name: account.username })
|
||||
: intl.formatMessage(messages.subscribe, { name: account.username });
|
||||
|
||||
const onSubscribeSuccess = () =>
|
||||
toast.success(intl.formatMessage(messages.subscribeSuccess));
|
||||
|
||||
const onSubscribeFailure = () =>
|
||||
toast.error(intl.formatMessage(messages.subscribeFailure));
|
||||
|
||||
const onUnsubscribeSuccess = () =>
|
||||
toast.success(intl.formatMessage(messages.unsubscribeSuccess));
|
||||
|
||||
const onUnsubscribeFailure = () =>
|
||||
toast.error(intl.formatMessage(messages.unsubscribeFailure));
|
||||
|
||||
const onNotifyToggle = () => {
|
||||
if (account.relationship?.notifying) {
|
||||
follow(account.id, { notify: false })
|
||||
?.then(() => onUnsubscribeSuccess())
|
||||
.catch(() => onUnsubscribeFailure());
|
||||
?.then(() => toast.success(intl.formatMessage(messages.unsubscribeSuccess)))
|
||||
.catch(() => toast.error(intl.formatMessage(messages.unsubscribeFailure)));
|
||||
} else {
|
||||
follow(account.id, { notify: true })
|
||||
?.then(() => onSubscribeSuccess())
|
||||
.catch(() => onSubscribeFailure());
|
||||
}
|
||||
};
|
||||
|
||||
const onSubscriptionToggle = () => {
|
||||
if (account.relationship?.subscribing) {
|
||||
dispatch(unsubscribeAccount(account.id))
|
||||
?.then(() => onUnsubscribeSuccess())
|
||||
.catch(() => onUnsubscribeFailure());
|
||||
} else {
|
||||
dispatch(subscribeAccount(account.id))
|
||||
?.then(() => onSubscribeSuccess())
|
||||
.catch(() => onSubscribeFailure());
|
||||
?.then(() => toast.success(intl.formatMessage(messages.subscribeSuccess)))
|
||||
.catch(() => toast.error(intl.formatMessage(messages.subscribeFailure)));
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggle = () => {
|
||||
if (features.accountNotifies) {
|
||||
onNotifyToggle();
|
||||
} else {
|
||||
onSubscriptionToggle();
|
||||
}
|
||||
onNotifyToggle();
|
||||
};
|
||||
|
||||
if (!features.accountSubscriptions && !features.accountNotifies) {
|
||||
if (!features.accountNotifies) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,6 @@ import {
|
||||
HomeTimeline,
|
||||
Followers,
|
||||
Following,
|
||||
DirectTimeline,
|
||||
Conversations,
|
||||
HashtagTimeline,
|
||||
Notifications,
|
||||
@@ -181,10 +180,7 @@ const SwitchingColumnsArea: React.FC<ISwitchingColumnsArea> = ({ children }) =>
|
||||
{features.federating && <WrappedRoute path='/timeline/:instance' exact page={RemoteInstancePage} component={RemoteTimeline} content={children} />}
|
||||
|
||||
{features.conversations && <WrappedRoute path='/conversations' page={DefaultPage} component={Conversations} content={children} />}
|
||||
{features.directTimeline && <WrappedRoute path='/messages' page={DefaultPage} component={DirectTimeline} content={children} />}
|
||||
{(features.conversations && !features.directTimeline) && (
|
||||
<WrappedRoute path='/messages' page={DefaultPage} component={Conversations} content={children} />
|
||||
)}
|
||||
{features.conversations && <Redirect from='/messages' to='/conversations' />}
|
||||
|
||||
{/* Mastodon web routes */}
|
||||
<Redirect from='/web/:path1/:path2/:path3' to='/:path1/:path2/:path3' />
|
||||
|
||||
@@ -9,7 +9,6 @@ export const PublicTimeline = lazy(() => import('soapbox/features/public-timelin
|
||||
export const RemoteTimeline = lazy(() => import('soapbox/features/remote-timeline'));
|
||||
export const CommunityTimeline = lazy(() => import('soapbox/features/community-timeline'));
|
||||
export const HashtagTimeline = lazy(() => import('soapbox/features/hashtag-timeline'));
|
||||
export const DirectTimeline = lazy(() => import('soapbox/features/direct-timeline'));
|
||||
export const Conversations = lazy(() => import('soapbox/features/conversations'));
|
||||
export const ListTimeline = lazy(() => import('soapbox/features/list-timeline'));
|
||||
export const Lists = lazy(() => import('soapbox/features/lists'));
|
||||
|
||||
Reference in New Issue
Block a user