Convert instance to use zod

This commit is contained in:
Alex Gleason
2023-09-23 20:41:24 -05:00
parent 970ad24de9
commit 3b630ed8fb
46 changed files with 326 additions and 195 deletions

View File

@@ -4,10 +4,9 @@ import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
import { updateConfig } from 'soapbox/actions/admin';
import { RadioGroup, RadioItem } from 'soapbox/components/radio';
import { useAppDispatch, useInstance } from 'soapbox/hooks';
import { Instance } from 'soapbox/schemas';
import toast from 'soapbox/toast';
import type { Instance } from 'soapbox/types/entities';
type RegistrationMode = 'open' | 'approval' | 'closed';
const messages = defineMessages({

View File

@@ -41,11 +41,13 @@ const Dashboard: React.FC = () => {
const v = parseVersion(instance.version);
const userCount = instance.stats.get('user_count');
const statusCount = instance.stats.get('status_count');
const domainCount = instance.stats.get('domain_count');
const {
user_count: userCount,
status_count: statusCount,
domain_count: domainCount,
} = instance.stats;
const mau = instance.pleroma.getIn(['stats', 'mau']) as number | undefined;
const mau = instance.pleroma.stats.mau;
const retention = (userCount && mau) ? Math.round(mau / userCount * 100) : undefined;
if (!account) return null;

View File

@@ -1,4 +1,3 @@
import { List as ImmutableList } from 'immutable';
import React from 'react';
import { FormattedMessage } from 'react-intl';
@@ -13,9 +12,9 @@ interface IConsumersList {
/** Displays OAuth consumers to log in with. */
const ConsumersList: React.FC<IConsumersList> = () => {
const instance = useInstance();
const providers = ImmutableList<string>(instance.pleroma.get('oauth_consumer_strategies'));
const providers = instance.pleroma.oauth_consumer_strategies;
if (providers.size > 0) {
if (providers.length > 0) {
return (
<Card className='bg-gray-50 p-4 dark:bg-primary-800 sm:rounded-xl'>
<Text size='xs' theme='muted'>

View File

@@ -46,11 +46,11 @@ const RegistrationForm: React.FC<IRegistrationForm> = ({ inviteToken }) => {
const instance = useInstance();
const locale = settings.get('locale');
const needsConfirmation = !!instance.pleroma.getIn(['metadata', 'account_activation_required']);
const needsConfirmation = instance.pleroma.metadata.account_activation_required;
const needsApproval = instance.approval_required;
const supportsEmailList = features.emailList;
const supportsAccountLookup = features.accountLookup;
const birthdayRequired = instance.pleroma.getIn(['metadata', 'birthday_required']);
const birthdayRequired = instance.pleroma.metadata.birthday_required;
const [captchaLoading, setCaptchaLoading] = useState(true);
const [submissionLoading, setSubmissionLoading] = useState(false);

View File

@@ -3,8 +3,8 @@ import React from 'react';
import { VirtuosoMockContext } from 'react-virtuoso';
import { ChatContext } from 'soapbox/contexts/chat-context';
import { buildAccount } from 'soapbox/jest/factory';
import { normalizeChatMessage, normalizeInstance } from 'soapbox/normalizers';
import { buildAccount, buildInstance } from 'soapbox/jest/factory';
import { normalizeChatMessage } from 'soapbox/normalizers';
import { ChatMessage } from 'soapbox/types/entities';
import { __stub } from '../../../../api';
@@ -70,7 +70,7 @@ Object.assign(navigator, {
const store = rootState
.set('me', '1')
.set('instance', normalizeInstance({ version: '3.4.1 (compatible; TruthSocial 1.0.0+unreleased)' }));
.set('instance', buildInstance({ version: '3.4.1 (compatible; TruthSocial 1.0.0+unreleased)' }));
const renderComponentWithChatContext = () => render(
<VirtuosoMockContext.Provider value={{ viewportHeight: 300, itemHeight: 100 }}>

View File

@@ -76,8 +76,8 @@ const ChatComposer = React.forwardRef<HTMLTextAreaElement | null, IChatComposer>
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.getIn(['configuration', 'chats', 'max_characters']) as number);
const attachmentLimit = useAppSelector(state => state.instance.configuration.getIn(['chats', 'max_media_attachments']) as number);
const maxCharacterCount = useAppSelector((state) => state.instance.configuration.chats.max_characters);
const attachmentLimit = useAppSelector(state => state.instance.configuration.chats.max_media_attachments);
const [suggestions, setSuggestions] = useState<Suggestion>(initialSuggestionState);
const isSuggestionsAvailable = suggestions.list.length > 0;

View File

@@ -53,7 +53,7 @@ const Chat: React.FC<ChatInterface> = ({ chat, inputRef, className }) => {
const dispatch = useAppDispatch();
const { createChatMessage, acceptChat } = useChatActions(chat.id);
const attachmentLimit = useAppSelector(state => state.instance.configuration.getIn(['chats', 'max_media_attachments']) as number);
const attachmentLimit = useAppSelector(state => state.instance.configuration.chats.max_media_attachments);
const [content, setContent] = useState<string>('');
const [attachments, setAttachments] = useState<Attachment[]>([]);

View File

@@ -77,7 +77,7 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
const compose = useCompose(id);
const showSearch = useAppSelector((state) => state.search.submitted && !state.search.hidden);
const maxTootChars = configuration.getIn(['statuses', 'max_characters']) as number;
const maxTootChars = configuration.statuses.max_characters;
const scheduledStatusCount = useAppSelector((state) => state.scheduled_statuses.size);
const features = useFeatures();

View File

@@ -8,7 +8,6 @@ import { useAppDispatch, useCompose, useInstance } from 'soapbox/hooks';
import DurationSelector from './duration-selector';
import type { Map as ImmutableMap } from 'immutable';
import type { AutoSuggestion } from 'soapbox/components/autosuggest-input';
const messages = defineMessages({
@@ -115,13 +114,14 @@ const PollForm: React.FC<IPollForm> = ({ composeId }) => {
const compose = useCompose(composeId);
const pollLimits = configuration.get('polls') as ImmutableMap<string, number>;
const options = compose.poll?.options;
const expiresIn = compose.poll?.expires_in;
const isMultiple = compose.poll?.multiple;
const maxOptions = pollLimits.get('max_options') as number;
const maxOptionChars = pollLimits.get('max_characters_per_option') as number;
const {
max_options: maxOptions,
max_characters_per_option: maxOptionChars,
} = configuration.polls;
const onRemoveOption = (index: number) => dispatch(removePollOption(composeId, index));
const onChangeOption = (index: number, title: string) => dispatch(changePollOption(composeId, index, title));

View File

@@ -4,14 +4,12 @@ import { defineMessages, IntlShape, useIntl } from 'react-intl';
import { IconButton } from 'soapbox/components/ui';
import { useInstance } from 'soapbox/hooks';
import type { List as ImmutableList } from 'immutable';
const messages = defineMessages({
upload: { id: 'upload_button.label', defaultMessage: 'Add media attachment' },
});
export const onlyImages = (types: ImmutableList<string>) => {
return Boolean(types && types.every(type => type.startsWith('image/')));
export const onlyImages = (types: string[] | undefined): boolean => {
return types?.every((type) => type.startsWith('image/')) ?? false;
};
export interface IUploadButton {
@@ -38,7 +36,7 @@ const UploadButton: React.FC<IUploadButton> = ({
const { configuration } = useInstance();
const fileElement = useRef<HTMLInputElement>(null);
const attachmentTypes = configuration.getIn(['media_attachments', 'supported_mime_types']) as ImmutableList<string>;
const attachmentTypes = configuration.media_attachments.supported_mime_types;
const handleChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
if (e.target.files?.length) {
@@ -78,7 +76,7 @@ const UploadButton: React.FC<IUploadButton> = ({
ref={fileElement}
type='file'
multiple
accept={attachmentTypes && attachmentTypes.toArray().join(',')}
accept={attachmentTypes?.join(',')}
onChange={handleChange}
disabled={disabled}
className='hidden'

View File

@@ -8,7 +8,7 @@ import { useDraggedFiles } from 'soapbox/hooks';
interface IMediaInput {
className?: string
src: string | undefined
accept: string
accept?: string
onChange: (files: FileList | null) => void
disabled?: boolean
}

View File

@@ -11,7 +11,7 @@ const messages = defineMessages({
interface IMediaInput {
src: string | undefined
accept: string
accept?: string
onChange: (files: FileList | null) => void
onClear?: () => void
disabled?: boolean

View File

@@ -25,7 +25,6 @@ import { isDefaultAvatar, isDefaultHeader } from 'soapbox/utils/accounts';
import AvatarPicker from './components/avatar-picker';
import HeaderPicker from './components/header-picker';
import type { List as ImmutableList } from 'immutable';
import type { StreamfieldComponent } from 'soapbox/components/ui/streamfield/streamfield';
import type { Account } from 'soapbox/schemas';
@@ -183,11 +182,12 @@ const EditProfile: React.FC = () => {
const { account } = useOwnAccount();
const features = useFeatures();
const maxFields = instance.pleroma.getIn(['metadata', 'fields_limits', 'max_fields'], 4) as number;
const maxFields = instance.pleroma.metadata.fields_limits.max_fields;
const attachmentTypes = useAppSelector(
state => state.instance.configuration.getIn(['media_attachments', 'supported_mime_types']) as ImmutableList<string>,
)?.filter(type => type.startsWith('image/')).toArray().join(',');
state => state.instance.configuration.media_attachments.supported_mime_types)
?.filter(type => type.startsWith('image/'))
.join(',');
const [isLoading, setLoading] = useState(false);
const [data, setData] = useState<AccountCredentials>({});

View File

@@ -111,7 +111,7 @@ const InstanceRestrictions: React.FC<IInstanceRestrictions> = ({ remoteInstance
if (!instance || !remoteInstance) return null;
const host = remoteInstance.get('host');
const siteTitle = instance.get('title');
const siteTitle = instance.title;
if (remoteInstance.getIn(['federation', 'reject']) === true) {
return (

View File

@@ -13,8 +13,6 @@ import HeaderPicker from '../edit-profile/components/header-picker';
import GroupTagsField from './components/group-tags-field';
import type { List as ImmutableList } from 'immutable';
const nonDefaultAvatar = (url: string | undefined) => url && isDefaultAvatar(url) ? undefined : url;
const nonDefaultHeader = (url: string | undefined) => url && isDefaultHeader(url) ? undefined : url;
@@ -48,12 +46,12 @@ const EditGroup: React.FC<IEditGroup> = ({ params: { groupId } }) => {
const displayName = useTextField(group?.display_name);
const note = useTextField(group?.note_plain);
const maxName = Number(instance.configuration.getIn(['groups', 'max_characters_name']));
const maxNote = Number(instance.configuration.getIn(['groups', 'max_characters_description']));
const maxName = Number(instance.configuration.groups.max_characters_name);
const maxNote = Number(instance.configuration.groups.max_characters_description);
const attachmentTypes = useAppSelector(
state => state.instance.configuration.getIn(['media_attachments', 'supported_mime_types']) as ImmutableList<string>,
)?.filter(type => type.startsWith('image/')).toArray().join(',');
const attachmentTypes = useAppSelector(state => state.instance.configuration.media_attachments.supported_mime_types)
?.filter((type) => type.startsWith('image/'))
.join(',');
async function handleSubmit() {
setIsSubmitting(true);

View File

@@ -23,7 +23,7 @@ const Migration = () => {
const dispatch = useAppDispatch();
const instance = useInstance();
const cooldownPeriod = instance.pleroma.getIn(['metadata', 'migration_cooldown_period']) as number | undefined;
const cooldownPeriod = instance.pleroma.metadata.migration_cooldown_period;
const [targetAccount, setTargetAccount] = useState('');
const [password, setPassword] = useState('');

View File

@@ -5,8 +5,6 @@ import Icon from 'soapbox/components/icon';
import { HStack, Text } from 'soapbox/components/ui';
import { useAppSelector } from 'soapbox/hooks';
import type { List as ImmutableList } from 'immutable';
interface IUploadButton {
disabled?: boolean
onSelectFile: (files: FileList) => void
@@ -14,7 +12,8 @@ interface IUploadButton {
const UploadButton: React.FC<IUploadButton> = ({ disabled, onSelectFile }) => {
const fileElement = useRef<HTMLInputElement>(null);
const attachmentTypes = useAppSelector(state => state.instance.configuration.getIn(['media_attachments', 'supported_mime_types']) as ImmutableList<string>)?.filter(type => type.startsWith('image/'));
const attachmentTypes = useAppSelector(state => state.instance.configuration.media_attachments.supported_mime_types)
?.filter((type) => type.startsWith('image/'));
const handleChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
if (e.target.files?.length) {
@@ -40,7 +39,7 @@ const UploadButton: React.FC<IUploadButton> = ({ disabled, onSelectFile }) => {
<input
ref={fileElement}
type='file'
accept={attachmentTypes && attachmentTypes.toArray().join(',')}
accept={attachmentTypes?.join(',')}
onChange={handleChange}
disabled={disabled}
className='hidden'

View File

@@ -10,8 +10,6 @@ import { useAppSelector, useDebounce, useInstance } from 'soapbox/hooks';
import { usePreview } from 'soapbox/hooks/forms';
import resizeImage from 'soapbox/utils/resize-image';
import type { List as ImmutableList } from 'immutable';
const messages = defineMessages({
groupNamePlaceholder: { id: 'manage_group.fields.name_placeholder', defaultMessage: 'Group Name' },
groupDescriptionPlaceholder: { id: 'manage_group.fields.description_placeholder', defaultMessage: 'Description' },
@@ -40,9 +38,9 @@ const DetailsStep: React.FC<IDetailsStep> = ({ params, onChange }) => {
const avatarSrc = usePreview(params.avatar);
const headerSrc = usePreview(params.header);
const attachmentTypes = useAppSelector(
state => state.instance.configuration.getIn(['media_attachments', 'supported_mime_types']) as ImmutableList<string>,
)?.filter(type => type.startsWith('image/')).toArray().join(',');
const attachmentTypes = useAppSelector(state => state.instance.configuration.media_attachments.supported_mime_types)
?.filter((type) => type.startsWith('image/'))
.join(',');
const handleTextChange = (property: keyof CreateGroupParams): React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement> => {
return (e) => {
@@ -107,7 +105,7 @@ const DetailsStep: React.FC<IDetailsStep> = ({ params, onChange }) => {
placeholder={intl.formatMessage(messages.groupNamePlaceholder)}
value={displayName}
onChange={handleTextChange('display_name')}
maxLength={Number(instance.configuration.getIn(['groups', 'max_characters_name']))}
maxLength={Number(instance.configuration.groups.max_characters_name)}
/>
</FormGroup>
@@ -119,7 +117,7 @@ const DetailsStep: React.FC<IDetailsStep> = ({ params, onChange }) => {
placeholder={intl.formatMessage(messages.groupDescriptionPlaceholder)}
value={note}
onChange={handleTextChange('note')}
maxLength={Number(instance.configuration.getIn(['groups', 'max_characters_description']))}
maxLength={Number(instance.configuration.groups.max_characters_description)}
/>
</FormGroup>