Fix types, at least

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak
2024-08-07 16:23:44 +02:00
parent 0fc8a2993f
commit 7fb07b66be
42 changed files with 204 additions and 269 deletions

View File

@@ -13,6 +13,8 @@ import { Checkbox, Form, FormGroup, FormActions, Button, Input, Textarea, Select
import CaptchaField from 'soapbox/features/auth-login/components/captcha';
import { useAppDispatch, useSettings, useFeatures, useInstance } from 'soapbox/hooks';
import type { CreateAccountParams } from 'pl-api';
const messages = defineMessages({
username: { id: 'registration.fields.username_placeholder', defaultMessage: 'Username' },
username_hint: { id: 'registration.fields.username_hint', defaultMessage: 'Only letters, numbers, and underscores are allowed.' },
@@ -53,7 +55,13 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
const [captchaLoading, setCaptchaLoading] = useState(true);
const [submissionLoading, setSubmissionLoading] = useState(false);
const [params, setParams] = useState(ImmutableMap<string, any>());
const [params, setParams] = useState<CreateAccountParams>({
username: '',
email: '',
password: '',
agreement: false,
locale: '',
});
const [captchaIdempotencyKey, setCaptchaIdempotencyKey] = useState(uuidv4());
const [usernameUnavailable, setUsernameUnavailable] = useState(false);
const [passwordConfirmation, setPasswordConfirmation] = useState('');
@@ -61,39 +69,35 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
const controller = useRef(new AbortController());
const updateParams = (map: any) => {
setParams(params.merge(ImmutableMap(map)));
};
const onInputChange: React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement> = e => {
updateParams({ [e.target.name]: e.target.value });
setParams(params => ({ ...params, [e.target.name]: e.target.value }));
};
const onUsernameChange: React.ChangeEventHandler<HTMLInputElement> = e => {
updateParams({ username: e.target.value });
setParams(params => ({ ...params, username: e.target.value }));
setUsernameUnavailable(false);
controller.current.abort();
controller.current = new AbortController();
const domain = params.get('domain');
const domain = params.domain;
usernameAvailable(e.target.value, domain ? domains!.find(({ id }) => id === domain)?.domain : undefined);
};
const onDomainChange: React.ChangeEventHandler<HTMLSelectElement> = e => {
updateParams({ domain: e.target.value || null });
setParams(params => ({ ...params, domain: e.target.value || undefined }));
setUsernameUnavailable(false);
controller.current.abort();
controller.current = new AbortController();
const username = params.get('username');
const username = params.username;
if (username) {
usernameAvailable(username, domains!.find(({ id }) => id === e.target.value)?.domain);
}
};
const onCheckboxChange: React.ChangeEventHandler<HTMLInputElement> = e => {
updateParams({ [e.target.name]: e.target.checked });
setParams(params => ({ ...params, [e.target.name]: e.target.checked }));
};
const onPasswordChange: React.ChangeEventHandler<HTMLInputElement> = e => {
@@ -106,7 +110,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
};
const onPasswordConfirmChange: React.ChangeEventHandler<HTMLInputElement> = e => {
const password = params.get('password', '');
const password = params.password || '';
const passwordConfirmation = e.target.value;
setPasswordConfirmation(passwordConfirmation);
@@ -120,7 +124,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
};
const onBirthdayChange = (birthday: string) => {
updateParams({ birthday });
setParams(params => ({ ...params, birthday }));
};
const launchModal = () => {
@@ -129,7 +133,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
<FormattedMessage
id='confirmations.register.needs_confirmation'
defaultMessage='Please check your inbox at {email} for confirmation instructions. You will need to verify your email address to continue.'
values={{ email: <strong>{params.get('email')}</strong> }}
values={{ email: <strong>{params.email}</strong> }}
/></p>}
{needsApproval && <p>
<FormattedMessage
@@ -160,7 +164,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
}
};
const passwordsMatch = () => params.get('password', '') === passwordConfirmation;
const passwordsMatch = () => params.password === passwordConfirmation;
const usernameAvailable = useCallback(debounce((username, domain?: string) => {
if (!supportsAccountLookup) return;
@@ -186,19 +190,18 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
return;
}
const normalParams = params.withMutations(params => {
// Locale for confirmation email
params.set('locale', locale);
const normalParams = {
...params,
locale,
};
// Pleroma invites
if (inviteToken) {
params.set('token', inviteToken);
}
});
if (inviteToken) {
params.token = inviteToken;
}
setSubmissionLoading(true);
dispatch(register(normalParams.toJS()))
dispatch(register(normalParams))
.then(postRegisterAction)
.catch(() => {
setSubmissionLoading(false);
@@ -212,10 +215,11 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
const onFetchCaptcha = (captcha: ImmutableMap<string, any>) => {
setCaptchaLoading(false);
updateParams({
setParams(params => ({
...params,
captcha_token: captcha.get('token'),
captcha_answer_data: captcha.get('answer_data'),
});
}));
};
const onFetchCaptchaFail = () => {
@@ -224,7 +228,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
const refreshCaptcha = () => {
setCaptchaIdempotencyKey(uuidv4());
updateParams({ captcha_solution: '' });
setParams(params => ({ ...params, captcha_solution: '' }));
};
const isLoading = captchaLoading || submissionLoading;
@@ -247,7 +251,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
pattern='^[a-zA-Z\d_-]+'
icon={require('@tabler/icons/outline/at.svg')}
onChange={onUsernameChange}
value={params.get('username', '')}
value={params.username}
required
/>
</FormGroup>
@@ -256,7 +260,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
<FormGroup>
<Select
onChange={onDomainChange}
value={params.get('domain')}
value={params.domain}
>
{domains.map(({ id, domain }) => (
<option key={id} value={id}>{domain}</option>
@@ -273,7 +277,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
autoCorrect='off'
autoCapitalize='off'
onChange={onInputChange}
value={params.get('email', '')}
value={params.email}
required
/>
@@ -285,7 +289,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
autoCorrect='off'
autoCapitalize='off'
onChange={onPasswordChange}
value={params.get('password', '')}
value={params.password}
required
/>
@@ -308,7 +312,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
{birthdayRequired && (
<BirthdayInput
value={params.get('birthday')}
value={params.birthday || ''}
onChange={onBirthdayChange}
required
/>
@@ -323,7 +327,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
placeholder={intl.formatMessage(messages.reasonHint)}
maxLength={500}
onChange={onInputChange}
value={params.get('reason', '')}
value={params.reason || ''}
autoGrow
required
/>
@@ -337,7 +341,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
onClick={onCaptchaClick}
idempotencyKey={captchaIdempotencyKey}
name='captcha_solution'
value={params.get('captcha_solution', '')}
value={params.captcha_solution || ''}
/>
<FormGroup
@@ -346,7 +350,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
<Checkbox
name='agreement'
onChange={onCheckboxChange}
checked={params.get('agreement', false)}
checked={params.agreement}
required
/>
</FormGroup>
@@ -356,7 +360,7 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
<Checkbox
name='accepts_email_list'
onChange={onCheckboxChange}
checked={params.get('accepts_email_list', false)}
checked={params.accepts_email_list}
/>
</FormGroup>
)}

View File

@@ -9,8 +9,9 @@ import { Avatar, HStack, IconButton, Stack, Text } from 'soapbox/components/ui';
import VerificationBadge from 'soapbox/components/verification-badge';
import { useChatContext } from 'soapbox/contexts/chat-context';
import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
import { IChat, useChatActions } from 'soapbox/queries/chats';
import { useChatActions } from 'soapbox/queries/chats';
import type { Chat } from 'pl-api';
import type { Menu } from 'soapbox/components/dropdown-menu';
const messages = defineMessages({
@@ -23,7 +24,7 @@ const messages = defineMessages({
});
interface IChatListItemInterface {
chat: IChat;
chat: Chat;
onClick: (chat: any) => void;
}

View File

@@ -5,10 +5,11 @@ import { Components, Virtuoso, VirtuosoHandle } from 'react-virtuoso';
import { Avatar, Button, Divider, Spinner, Stack, Text } from 'soapbox/components/ui';
import PlaceholderChatMessage from 'soapbox/features/placeholder/components/placeholder-chat-message';
import { useAppSelector } from 'soapbox/hooks';
import { IChat, useChatActions, useChatMessages } from 'soapbox/queries/chats';
import { useChatActions, useChatMessages } from 'soapbox/queries/chats';
import ChatMessage from './chat-message';
import type { Chat } from 'pl-api';
import type { ChatMessage as ChatMessageEntity } from 'soapbox/types/entities';
const messages = defineMessages({
@@ -21,7 +22,7 @@ const messages = defineMessages({
type TimeFormat = 'today' | 'date';
const timeChange = (prev: ChatMessageEntity, curr: ChatMessageEntity): TimeFormat | null => {
const timeChange = (prev: Pick<ChatMessageEntity, 'created_at'>, curr: Pick<ChatMessageEntity, 'created_at'>): TimeFormat | null => {
const prevDate = new Date(prev.created_at).getDate();
const currDate = new Date(curr.created_at).getDate();
const nowDate = new Date().getDate();
@@ -57,7 +58,7 @@ const Scroller: Components['Scroller'] = React.forwardRef((props, ref) => {
interface IChatMessageList {
/** Chat the messages are being rendered from. */
chat: IChat;
chat: Chat;
}
/** Scrollable list of chat messages. */

View File

@@ -11,11 +11,12 @@ import { HStack, Icon, Stack, Text } from 'soapbox/components/ui';
import emojify from 'soapbox/features/emoji';
import { MediaGallery } from 'soapbox/features/ui/util/async-components';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import { ChatKeys, IChat, useChatActions } from 'soapbox/queries/chats';
import { ChatKeys, useChatActions } from 'soapbox/queries/chats';
import { queryClient } from 'soapbox/queries/client';
import { stripHTML } from 'soapbox/utils/html';
import { onlyEmoji } from 'soapbox/utils/rich-content';
import type { Chat } from 'pl-api';
import type { Menu as IMenu } from 'soapbox/components/dropdown-menu';
import type { ChatMessage as ChatMessageEntity } from 'soapbox/types/entities';
@@ -43,7 +44,7 @@ const parseContent = (chatMessage: ChatMessageEntity) => {
};
interface IChatMessage {
chat: IChat;
chat: Chat;
chatMessage: ChatMessageEntity;
}

View File

@@ -3,10 +3,11 @@ import { defineMessages, useIntl } from 'react-intl';
import { useHistory } from 'react-router-dom';
import { CardTitle, HStack, IconButton, Stack } from 'soapbox/components/ui';
import { IChat } from 'soapbox/queries/chats';
import ChatList from '../../chat-list';
import type { Chat } from 'pl-api';
const messages = defineMessages({
title: { id: 'column.chats', defaultMessage: 'Chats' },
});
@@ -15,7 +16,7 @@ const ChatPageSidebar = () => {
const intl = useIntl();
const history = useHistory();
const handleClickChat = (chat: IChat) => {
const handleClickChat = (chat: Chat) => {
history.push(`/chats/${chat.id}`);
};

View File

@@ -4,7 +4,7 @@ import { FormattedMessage } from 'react-intl';
import { Stack } from 'soapbox/components/ui';
import { ChatWidgetScreens, useChatContext } from 'soapbox/contexts/chat-context';
import { useStatContext } from 'soapbox/contexts/stat-context';
import { IChat, useChats } from 'soapbox/queries/chats';
import { useChats } from 'soapbox/queries/chats';
import ChatList from '../chat-list';
import ChatSearch from '../chat-search/chat-search';
@@ -16,13 +16,15 @@ import { Pane } from '../ui';
import Blankslate from './blankslate';
import type { Chat } from 'pl-api';
const ChatPane = () => {
const { unreadChatsCount } = useStatContext();
const { screen, changeScreen, isOpen, toggleChatPane } = useChatContext();
const { chatsQuery: { data: chats, isLoading } } = useChats();
const handleClickChat = (nextChat: IChat) => {
const handleClickChat = (nextChat: Chat) => {
changeScreen(ChatWidgetScreens.CHAT, nextChat.id);
};

View File

@@ -6,7 +6,7 @@ import { Avatar, HStack, Stack, Text } from 'soapbox/components/ui';
import VerificationBadge from 'soapbox/components/verification-badge';
import useAccountSearch from 'soapbox/queries/search';
import type { Account } from 'soapbox/types/entities';
import type { Account } from 'pl-api';
interface IResults {
accountSearchResult: ReturnType<typeof useAccountSearch>;

View File

@@ -6,12 +6,13 @@ import { uploadMedia } from 'soapbox/actions/media';
import { Stack } from 'soapbox/components/ui';
import { useAppDispatch } from 'soapbox/hooks';
import { normalizeAttachment } from 'soapbox/normalizers';
import { IChat, useChatActions } from 'soapbox/queries/chats';
import { useChatActions } from 'soapbox/queries/chats';
import toast from 'soapbox/toast';
import ChatComposer from './chat-composer';
import ChatMessageList from './chat-message-list';
import type { Chat as ChatEntity } from 'pl-api';
import type { PlfeResponse } from 'soapbox/api';
import type { Attachment } from 'soapbox/types/entities';
@@ -23,7 +24,7 @@ const messages = defineMessages({
});
interface ChatInterface {
chat: IChat;
chat: ChatEntity;
inputRef?: MutableRefObject<HTMLTextAreaElement | null>;
className?: string;
}

View File

@@ -196,7 +196,7 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
const renderButtons = useCallback(() => (
<HStack alignItems='center' space={2}>
{features.media && <UploadButtonContainer composeId={id} />}
<UploadButtonContainer composeId={id} />
<EmojiPickerDropdown onPickEmoji={handleEmojiPick} condensed={shouldCondense} />
{features.polls && <PollButton composeId={id} />}
{features.scheduledStatuses && <ScheduleButton composeId={id} />}

View File

@@ -20,7 +20,7 @@ const ReplyMentions: React.FC<IReplyMentions> = ({ composeId }) => {
const status = useAppSelector<StatusEntity | null>(state => getStatus(state, { id: compose.in_reply_to! }));
const to = compose.to;
if (!features.explicitAddressing || !status || !to) {
if (!features.createStatusExplicitAddressing || !status || !to) {
return null;
}

View File

@@ -132,7 +132,7 @@ const Settings = () => {
<Preferences />
</CardBody>
{(features.security || features.accountAliases) && (
{(features.security || features.manageAccountAliases) && (
<>
<CardHeader>
<CardTitle title={intl.formatMessage(messages.other)} />

View File

@@ -2,15 +2,15 @@ import React, { useMemo, useState } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { z } from 'zod';
import { useCreateGroup, type CreateGroupParams } from 'soapbox/api/hooks';
import { useCreateGroup } from 'soapbox/api/hooks';
import { Modal, Stack } from 'soapbox/components/ui';
import { type Group } from 'soapbox/schemas';
import toast from 'soapbox/toast';
import ConfirmationStep from './steps/confirmation-step';
import DetailsStep from './steps/details-step';
import PrivacyStep from './steps/privacy-step';
import type { CreateGroupParams } from 'pl-api';
import type { PlfeResponse } from 'soapbox/api';
const messages = defineMessages({
@@ -22,7 +22,6 @@ const messages = defineMessages({
enum Steps {
ONE = 'ONE',
TWO = 'TWO',
THREE = 'THREE',
}
interface ICreateGroupModal {
@@ -34,7 +33,7 @@ const CreateGroupModal: React.FC<ICreateGroupModal> = ({ onClose }) => {
const [group, setGroup] = useState<Group | null>(null);
const [params, setParams] = useState<CreateGroupParams>({
group_visibility: 'everyone',
display_name: '',
});
const [currentStep, setCurrentStep] = useState<Steps>(Steps.ONE);
@@ -46,24 +45,19 @@ const CreateGroupModal: React.FC<ICreateGroupModal> = ({ onClose }) => {
const confirmationText = useMemo(() => {
switch (currentStep) {
case Steps.THREE:
return intl.formatMessage(messages.done);
case Steps.TWO:
return intl.formatMessage(messages.create);
return intl.formatMessage(messages.done);
default:
return intl.formatMessage(messages.next);
return intl.formatMessage(messages.create);
}
}, [currentStep]);
const handleNextStep = () => {
switch (currentStep) {
case Steps.ONE:
setCurrentStep(Steps.TWO);
break;
case Steps.TWO:
createGroup(params, {
onSuccess(group) {
setCurrentStep(Steps.THREE);
setCurrentStep(Steps.TWO);
setGroup(group);
},
onError(error: { response?: PlfeResponse }) {
@@ -74,7 +68,7 @@ const CreateGroupModal: React.FC<ICreateGroupModal> = ({ onClose }) => {
},
});
break;
case Steps.THREE:
case Steps.TWO:
handleClose();
break;
default:
@@ -85,26 +79,13 @@ const CreateGroupModal: React.FC<ICreateGroupModal> = ({ onClose }) => {
const renderStep = () => {
switch (currentStep) {
case Steps.ONE:
return <PrivacyStep params={params} onChange={setParams} />;
case Steps.TWO:
return <DetailsStep params={params} onChange={setParams} />;
case Steps.THREE:
case Steps.TWO:
return <ConfirmationStep group={group} />;
}
};
const renderModalTitle = () => {
switch (currentStep) {
case Steps.ONE:
return <FormattedMessage id='navigation_bar.create_group' defaultMessage='Create group' />;
default:
if (params.group_visibility === 'everyone') {
return <FormattedMessage id='navigation_bar.create_group.public' defaultMessage='Create public group' />;
} else {
return <FormattedMessage id='navigation_bar.create_group.private' defaultMessage='Create private group' />;
}
}
};
const renderModalTitle = () => <FormattedMessage id='navigation_bar.create_group' defaultMessage='Create group' />;
return (
<Modal

View File

@@ -1,7 +1,6 @@
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { CreateGroupParams } from 'soapbox/api/hooks';
import { Form, FormGroup, Input, Textarea } from 'soapbox/components/ui';
import AvatarPicker from 'soapbox/features/edit-profile/components/avatar-picker';
import HeaderPicker from 'soapbox/features/edit-profile/components/header-picker';
@@ -9,6 +8,8 @@ import { useAppSelector, useInstance } from 'soapbox/hooks';
import { usePreview } from 'soapbox/hooks/forms';
import resizeImage from 'soapbox/utils/resize-image';
import type { CreateGroupParams } from 'pl-api';
const messages = defineMessages({
groupNamePlaceholder: { id: 'manage_group.fields.name_placeholder', defaultMessage: 'Group Name' },
groupDescriptionPlaceholder: { id: 'manage_group.fields.description_placeholder', defaultMessage: 'Description' },
@@ -17,7 +18,7 @@ const messages = defineMessages({
interface IDetailsStep {
params: CreateGroupParams;
onChange(params: CreateGroupParams): void;
onChange: (params: CreateGroupParams) => void;
}
const DetailsStep: React.FC<IDetailsStep> = ({ params, onChange }) => {
@@ -52,7 +53,10 @@ const DetailsStep: React.FC<IDetailsStep> = ({ params, onChange }) => {
}
};
const handleImageClear = (property: keyof CreateGroupParams) => () => onChange({ [property]: undefined });
const handleImageClear = (property: keyof CreateGroupParams) => () => onChange({
...params,
[property]: undefined,
});
return (
<Form>

View File

@@ -1,58 +0,0 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { type CreateGroupParams } from 'soapbox/api/hooks';
import List, { ListItem } from 'soapbox/components/list';
import { Form, FormGroup, Stack, Text } from 'soapbox/components/ui';
interface IPrivacyStep {
params: CreateGroupParams;
onChange(params: CreateGroupParams): void;
}
const PrivacyStep: React.FC<IPrivacyStep> = ({ params, onChange }) => {
const visibility = params.group_visibility || 'everyone';
const onChangePrivacy = (group_visibility: CreateGroupParams['group_visibility']) => {
onChange({ ...params, group_visibility });
};
return (
<>
<Stack className='mx-auto max-w-xs py-10' space={2}>
<Text size='3xl' weight='bold' align='center'>
<FormattedMessage id='manage_group.get_started' defaultMessage='Lets get started!' />
</Text>
<Text theme='muted' align='center'>
<FormattedMessage id='manage_group.tagline' defaultMessage='Groups connect you with others based on shared interests.' />
</Text>
</Stack>
<Form>
<FormGroup
labelText={<FormattedMessage id='manage_group.privacy.label' defaultMessage='Privacy settings' />}
>
<List>
<ListItem
label={<Text weight='medium'><FormattedMessage id='manage_group.privacy.public.label' defaultMessage='Public' /></Text>}
hint={<FormattedMessage id='manage_group.privacy.public.hint' defaultMessage='Discoverable. Anyone can join.' />}
onSelect={() => onChangePrivacy('everyone')}
isSelected={visibility === 'everyone'}
/>
<ListItem
label={<Text weight='medium'><FormattedMessage id='manage_group.privacy.private.label' defaultMessage='Private (Owner approval required)' /></Text>}
hint={<FormattedMessage id='manage_group.privacy.private.hint' defaultMessage='Discoverable. Users can join after their request is approved.' />}
onSelect={() => onChangePrivacy('members_only')}
isSelected={visibility === 'members_only'}
/>
</List>
</FormGroup>
<Text size='sm' theme='muted' align='center'>
<FormattedMessage id='manage_group.privacy.hint' defaultMessage='These settings cannot be changed later.' />
</Text>
</Form>
</>
);
};
export { PrivacyStep as default };