diff --git a/src/actions/modals.ts b/src/actions/modals.ts index 4540b100e..08ee77763 100644 --- a/src/actions/modals.ts +++ b/src/actions/modals.ts @@ -1,20 +1,75 @@ import { AppDispatch } from 'soapbox/store'; -import type { MediaAttachment } from 'pl-api'; +import type { ICryptoAddress } from 'soapbox/features/crypto-donate/components/crypto-address'; import type { ModalType } from 'soapbox/features/ui/components/modal-root'; +import type { AccountModerationModalProps } from 'soapbox/features/ui/components/modals/account-moderation-modal/account-moderation-modal'; +import type { BoostModalProps } from 'soapbox/features/ui/components/modals/boost-modal'; +import type { CompareHistoryModalProps } from 'soapbox/features/ui/components/modals/compare-history-modal'; +import type { ComponentModalProps } from 'soapbox/features/ui/components/modals/component-modal'; +import type { ComposeModalProps } from 'soapbox/features/ui/components/modals/compose-modal'; +import type { ConfirmationModalProps } from 'soapbox/features/ui/components/modals/confirmation-modal'; +import type { DislikesModalProps } from 'soapbox/features/ui/components/modals/dislikes-modal'; +import type { EditAnnouncementModalProps } from 'soapbox/features/ui/components/modals/edit-announcement-modal'; +import type { EditBookmarkFolderModalProps } from 'soapbox/features/ui/components/modals/edit-bookmark-folder-modal'; +import type { EditDomainModalProps } from 'soapbox/features/ui/components/modals/edit-domain-modal'; +import type { EditFederationModalProps } from 'soapbox/features/ui/components/modals/edit-federation-modal'; +import type { EditRuleModalProps } from 'soapbox/features/ui/components/modals/edit-rule-modal'; +import type { EmbedModalProps } from 'soapbox/features/ui/components/modals/embed-modal'; +import type { EventMapModalProps } from 'soapbox/features/ui/components/modals/event-map-modal'; +import type { EventParticipantsModalProps } from 'soapbox/features/ui/components/modals/event-participants-modal'; +import type { FamiliarFollowersModalProps } from 'soapbox/features/ui/components/modals/familiar-followers-modal'; +import type { FavouritesModalProps } from 'soapbox/features/ui/components/modals/favourites-modal'; +import type { JoinEventModalProps } from 'soapbox/features/ui/components/modals/join-event-modal'; +import type { ListAdderModalProps } from 'soapbox/features/ui/components/modals/list-adder-modal'; +import type { ListEditorModalProps } from 'soapbox/features/ui/components/modals/list-editor-modal'; +import type { MediaModalProps } from 'soapbox/features/ui/components/modals/media-modal'; +import type { MentionsModalProps } from 'soapbox/features/ui/components/modals/mentions-modal'; +import type { MissingDescriptionModalProps } from 'soapbox/features/ui/components/modals/missing-description-modal'; +import type { ReactionsModalProps } from 'soapbox/features/ui/components/modals/reactions-modal'; +import type { ReblogsModalProps } from 'soapbox/features/ui/components/modals/reblogs-modal'; +import type { ReplyMentionsModalProps } from 'soapbox/features/ui/components/modals/reply-mentions-modal'; +import type { SelectBookmarkFolderModalProps } from 'soapbox/features/ui/components/modals/select-bookmark-folder-modal'; +import type { TextFieldModalProps } from 'soapbox/features/ui/components/modals/text-field-modal'; +import type { UnauthorizedModalProps } from 'soapbox/features/ui/components/modals/unauthorized-modal'; +import type { VideoModalProps } from 'soapbox/features/ui/components/modals/video-modal'; -const MODAL_OPEN = 'MODAL_OPEN'; -const MODAL_CLOSE = 'MODAL_CLOSE'; +const MODAL_OPEN = 'MODAL_OPEN' as const; +const MODAL_CLOSE = 'MODAL_CLOSE' as const; type OpenModalProps = - | [type: 'MEDIA', props: { - media: Array; - statusId?: string; - index: number; - time?: number; - }] - | [type: Exclude, props: any] - | [type: Exclude]; + | [type: 'ACCOUNT_MODERATION', props: AccountModerationModalProps] + | [type: 'BIRTHDAYS' | 'COMPOSE_EVENT' | 'CREATE_GROUP' | 'HOTKEYS' | 'REPORT'] + | [type: 'BOOST', props: BoostModalProps] + | [type: 'COMPARE_HISTORY', props: CompareHistoryModalProps] + | [type: 'COMPONENT', props: ComponentModalProps] + | [type: 'COMPOSE', props?: ComposeModalProps] + | [type: 'CONFIRM', props: ConfirmationModalProps] + | [type: 'CRYPTO_DONATE', props: ICryptoAddress] + | [type: 'DISLIKES', props: DislikesModalProps] + | [type: 'EDIT_ANNOUNCEMENT', props?: EditAnnouncementModalProps] + | [type: 'EDIT_BOOKMARK_FOLDER', props: EditBookmarkFolderModalProps] + | [type: 'EDIT_DOMAIN', props?: EditDomainModalProps] + | [type: 'EDIT_FEDERATION', props: EditFederationModalProps] + | [type: 'EDIT_RULE', props?: EditRuleModalProps] + | [type: 'EMBED', props: EmbedModalProps] + | [type: 'EVENT_MAP', props: EventMapModalProps] + | [type: 'EVENT_PARTICIPANTS', props: EventParticipantsModalProps] + | [type: 'FAMILIAR_FOLLOWERS', props: FamiliarFollowersModalProps] + | [type: 'FAVOURITES', props: FavouritesModalProps] + | [type: 'JOIN_EVENT', props: JoinEventModalProps] + | [type: 'LIST_ADDER', props: ListAdderModalProps] + | [type: 'LIST_EDITOR', props: ListEditorModalProps] + | [type: 'MEDIA', props: MediaModalProps] + | [type: 'MENTIONS', props: MentionsModalProps] + | [type: 'MISSING_DESCRIPTION', props: MissingDescriptionModalProps] + | [type: 'MUTE'] + | [type: 'REACTIONS', props: ReactionsModalProps] + | [type: 'REBLOGS', props: ReblogsModalProps] + | [type: 'REPLY_MENTIONS', props: ReplyMentionsModalProps] + | [type: 'SELECT_BOOKMARK_FOLDER', props: SelectBookmarkFolderModalProps] + | [type: 'TEXT_FIELD', props: TextFieldModalProps] + | [type: 'UNAUTHORIZED', props?: UnauthorizedModalProps] + | [type: 'VIDEO', props: VideoModalProps]; /** Open a modal of the given type */ const openModal = (...[type, props]: OpenModalProps) => { diff --git a/src/actions/moderation.tsx b/src/actions/moderation.tsx index 35ad6a4d7..ae218ffd2 100644 --- a/src/actions/moderation.tsx +++ b/src/actions/moderation.tsx @@ -58,7 +58,6 @@ const deactivateUserModal = (intl: IntlShape, accountId: string, afterConfirm = ); dispatch(openModal('CONFIRM', { - icon: require('@tabler/icons/outline/user-off.svg'), heading: intl.formatMessage(messages.deactivateUserHeading, { acct }), message, confirm: intl.formatMessage(messages.deactivateUserConfirm, { name }), @@ -96,7 +95,6 @@ const deleteUserModal = (intl: IntlShape, accountId: string, afterConfirm = () = const checkbox = local ? intl.formatMessage(messages.deleteLocalUserCheckbox) : false; dispatch(openModal('CONFIRM', { - icon: require('@tabler/icons/outline/user-minus.svg'), heading: intl.formatMessage(messages.deleteUserHeading, { acct }), message, confirm, @@ -118,7 +116,6 @@ const toggleStatusSensitivityModal = (intl: IntlShape, statusId: string, sensiti const acct = state.statuses.get(statusId)!.account.acct; dispatch(openModal('CONFIRM', { - icon: require('@tabler/icons/outline/alert-triangle.svg'), heading: intl.formatMessage(sensitive === false ? messages.markStatusSensitiveHeading : messages.markStatusNotSensitiveHeading), message: intl.formatMessage(sensitive === false ? messages.markStatusSensitivePrompt : messages.markStatusNotSensitivePrompt, { acct }), confirm: intl.formatMessage(sensitive === false ? messages.markStatusSensitiveConfirm : messages.markStatusNotSensitiveConfirm), @@ -138,7 +135,6 @@ const deleteStatusModal = (intl: IntlShape, statusId: string, afterConfirm = () const acct = state.statuses.get(statusId)!.account.acct; dispatch(openModal('CONFIRM', { - icon: require('@tabler/icons/outline/trash.svg'), heading: intl.formatMessage(messages.deleteStatusHeading), message: intl.formatMessage(messages.deleteStatusPrompt, { acct: {acct} }), confirm: intl.formatMessage(messages.deleteStatusConfirm), diff --git a/src/components/domain.tsx b/src/components/domain.tsx index 379c74574..a2e53562a 100644 --- a/src/components/domain.tsx +++ b/src/components/domain.tsx @@ -21,7 +21,6 @@ const Domain: React.FC = ({ domain }) => { // const onBlockDomain = () => { // dispatch(openModal('CONFIRM', { - // icon: require('@tabler/icons/outline/ban.svg'), // heading: , // message: {domain} }} />, // confirm: intl.formatMessage(messages.blockDomainConfirm), diff --git a/src/components/modal-root.tsx b/src/components/modal-root.tsx index d51ca7b81..1a35c963d 100644 --- a/src/components/modal-root.tsx +++ b/src/components/modal-root.tsx @@ -77,7 +77,6 @@ const ModalRoot: React.FC = ({ children, onCancel, onClose, type }) if (hasComposeContent && type === 'COMPOSE') { const isEditing = compose!.id !== null; dispatch(openModal('CONFIRM', { - icon: require('@tabler/icons/outline/trash.svg'), heading: isEditing ? : , @@ -102,7 +101,6 @@ const ModalRoot: React.FC = ({ children, onCancel, onClose, type }) } else if (hasEventComposeContent && type === 'COMPOSE_EVENT') { const isEditing = getState().compose_event.id !== null; dispatch(openModal('CONFIRM', { - icon: require('@tabler/icons/outline/trash.svg'), heading: isEditing ? : , diff --git a/src/components/status-action-bar.tsx b/src/components/status-action-bar.tsx index 8ddb043e1..1dd800523 100644 --- a/src/components/status-action-bar.tsx +++ b/src/components/status-action-bar.tsx @@ -30,6 +30,7 @@ import { getReactForStatus, reduceEmoji } from 'soapbox/utils/emoji-reacts'; import GroupPopover from './groups/popover/group-popover'; import type { Menu } from 'soapbox/components/dropdown-menu'; +import type { UnauthorizedModalAction } from 'soapbox/features/ui/components/modals/unauthorized-modal'; import type { Account, Group } from 'soapbox/normalizers'; import type { SelectedStatus } from 'soapbox/selectors'; @@ -163,7 +164,7 @@ const StatusActionBar: React.FC = ({ return null; } - const onOpenUnauthorizedModal = (action?: string) => { + const onOpenUnauthorizedModal = (action?: UnauthorizedModalAction) => { dispatch(openModal('UNAUTHORIZED', { action, ap_id: status.url, @@ -219,7 +220,7 @@ const StatusActionBar: React.FC = ({ if ((e && e.shiftKey) || !boostModal) { modalReblog(); } else { - dispatch(openModal('BOOST', { status, onReblog: modalReblog })); + dispatch(openModal('BOOST', { statusId: status.id, onReblog: modalReblog })); } } else { onOpenUnauthorizedModal('REBLOG'); @@ -240,7 +241,6 @@ const StatusActionBar: React.FC = ({ dispatch(deleteStatus(status.id, withRedraft)); } else { dispatch(openModal('CONFIRM', { - icon: withRedraft ? require('@tabler/icons/outline/edit.svg') : require('@tabler/icons/outline/trash.svg'), heading: intl.formatMessage(withRedraft ? messages.redraftHeading : messages.deleteHeading), message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage), confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm), @@ -291,7 +291,6 @@ const StatusActionBar: React.FC = ({ const account = status.account; dispatch(openModal('CONFIRM', { - icon: require('@tabler/icons/outline/ban.svg'), heading: , message: @{account.acct} }} />, confirm: intl.formatMessage(messages.blockConfirm), diff --git a/src/components/status-reply-mentions.tsx b/src/components/status-reply-mentions.tsx index c43f61b9c..672900ea7 100644 --- a/src/components/status-reply-mentions.tsx +++ b/src/components/status-reply-mentions.tsx @@ -10,7 +10,7 @@ import { useAppDispatch } from 'soapbox/hooks'; import type { Status } from 'soapbox/normalizers'; interface IStatusReplyMentions { - status: Pick; + status: Pick; hoverable?: boolean; } @@ -20,12 +20,7 @@ const StatusReplyMentions: React.FC = ({ status, hoverable const handleOpenMentionsModal: React.MouseEventHandler = (e) => { e.stopPropagation(); - const account = status.account; - - dispatch(openModal('MENTIONS', { - username: account.acct, - statusId: status.id, - })); + dispatch(openModal('MENTIONS', { statusId: status.id })); }; if (!status.in_reply_to_id) { diff --git a/src/components/status.tsx b/src/components/status.tsx index f03a874bb..c771530d1 100644 --- a/src/components/status.tsx +++ b/src/components/status.tsx @@ -130,7 +130,7 @@ const Status: React.FC = (props) => { if (firstAttachment) { if (firstAttachment.type === 'video') { - dispatch(openModal('VIDEO', { status, media: firstAttachment, time: 0 })); + dispatch(openModal('VIDEO', { statusId: status.id, media: firstAttachment, time: 0 })); } else { dispatch(openModal('MEDIA', { statusId: status.id, media: status.media_attachments, index: 0 })); } @@ -151,7 +151,7 @@ const Status: React.FC = (props) => { if ((e && e.shiftKey) || !boostModal) { modalReblog(); } else { - dispatch(openModal('BOOST', { status: actualStatus, onReblog: modalReblog })); + dispatch(openModal('BOOST', { statusId: actualStatus.id, onReblog: modalReblog })); } }; diff --git a/src/features/account-gallery/index.tsx b/src/features/account-gallery/index.tsx index 569f21aaa..5aca4a325 100644 --- a/src/features/account-gallery/index.tsx +++ b/src/features/account-gallery/index.tsx @@ -64,7 +64,7 @@ const AccountGallery = () => { const handleOpenMedia = (attachment: AccountGalleryAttachment) => { if (attachment.type === 'video') { - dispatch(openModal('VIDEO', { media: attachment, status: attachment.status, account: attachment.account })); + dispatch(openModal('VIDEO', { media: attachment, statusId: attachment.status.id, account: attachment.account })); } else { const media = attachment.status.media_attachments; const index = media.findIndex((x) => x.id === attachment.id); diff --git a/src/features/account/components/header.tsx b/src/features/account/components/header.tsx index d5d0ee159..70c0595ae 100644 --- a/src/features/account/components/header.tsx +++ b/src/features/account/components/header.tsx @@ -133,7 +133,6 @@ const Header: React.FC = ({ account }) => { dispatch(unblockAccount(account.id)); } else { dispatch(openModal('CONFIRM', { - icon: require('@tabler/icons/outline/ban.svg'), heading: , message: @{account.acct} }} />, confirm: intl.formatMessage(messages.blockConfirm), @@ -195,7 +194,6 @@ const Header: React.FC = ({ account }) => { const onBlockDomain = (domain: string) => { dispatch(openModal('CONFIRM', { - icon: require('@tabler/icons/outline/ban.svg'), heading: , message: {domain} }} />, confirm: intl.formatMessage(messages.blockDomainConfirm), @@ -226,6 +224,7 @@ const Header: React.FC = ({ account }) => { const unfollowModal = getSettings(getState()).get('unfollowModal'); if (unfollowModal) { dispatch(openModal('CONFIRM', { + heading: @{account.acct} }} />, message: @{account.acct} }} />, confirm: intl.formatMessage(messages.removeFromFollowersConfirm), onConfirm: () => dispatch(removeFromFollowers(account.id)), diff --git a/src/features/auth-login/components/registration-form.tsx b/src/features/auth-login/components/registration-form.tsx index a440ddb0e..5e261fc0e 100644 --- a/src/features/auth-login/components/registration-form.tsx +++ b/src/features/auth-login/components/registration-form.tsx @@ -143,7 +143,6 @@ const RegistrationForm: React.FC = ({ inviteToken }) => { ); dispatch(openModal('CONFIRM', { - icon: require('@tabler/icons/outline/check.svg'), heading: needsConfirmation ? intl.formatMessage(messages.needsConfirmationHeader) : needsApproval @@ -151,6 +150,7 @@ const RegistrationForm: React.FC = ({ inviteToken }) => { : undefined, message, confirm: intl.formatMessage(messages.close), + onConfirm: () => {}, })); }; diff --git a/src/features/auth-token-list/index.tsx b/src/features/auth-token-list/index.tsx index 2fac46308..c219a28c4 100644 --- a/src/features/auth-token-list/index.tsx +++ b/src/features/auth-token-list/index.tsx @@ -28,7 +28,6 @@ const AuthToken: React.FC = ({ token, isCurrent }) => { const handleRevoke = () => { if (isCurrent) dispatch(openModal('CONFIRM', { - icon: require('@tabler/icons/outline/alert-triangle.svg'), heading: intl.formatMessage(messages.revokeSessionHeading), message: intl.formatMessage(messages.revokeSessionMessage), confirm: intl.formatMessage(messages.revokeSessionConfirm), diff --git a/src/features/bookmarks/index.tsx b/src/features/bookmarks/index.tsx index a30bce1bc..eeade3c04 100644 --- a/src/features/bookmarks/index.tsx +++ b/src/features/bookmarks/index.tsx @@ -61,6 +61,7 @@ const Bookmarks: React.FC = ({ params }) => { const handleRefresh = () => dispatch(fetchBookmarkedStatuses(folderId)); const handleEditFolder = () => { + if (!folderId) return; dispatch(openModal('EDIT_BOOKMARK_FOLDER', { folderId })); }; diff --git a/src/features/compose/containers/upload-button-container.ts b/src/features/compose/containers/upload-button-container.ts deleted file mode 100644 index 8742eb813..000000000 --- a/src/features/compose/containers/upload-button-container.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { connect } from 'react-redux'; - -import { uploadCompose } from 'soapbox/actions/compose'; - -import UploadButton from '../components/upload-button'; - -import type { IntlShape } from 'react-intl'; -import type { AppDispatch, RootState } from 'soapbox/store'; - -const mapStateToProps = (state: RootState, { composeId }: { composeId: string }) => ({ - disabled: state.compose.get(composeId)?.is_uploading, - resetFileKey: state.compose.get(composeId)?.resetFileKey!, -}); - -const mapDispatchToProps = (dispatch: AppDispatch, { composeId }: { composeId: string }) => ({ - - onSelectFile(files: FileList, intl: IntlShape) { - dispatch(uploadCompose(composeId, files, intl)); - }, - -}); - -const UploadButtonContainer = connect(mapStateToProps, mapDispatchToProps)(UploadButton); - -export { UploadButtonContainer as default }; diff --git a/src/features/compose/containers/upload-button-container.tsx b/src/features/compose/containers/upload-button-container.tsx new file mode 100644 index 000000000..2a0f828e2 --- /dev/null +++ b/src/features/compose/containers/upload-button-container.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +import { uploadCompose } from 'soapbox/actions/compose'; +import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; + +import UploadButton from '../components/upload-button'; + +import type { IntlShape } from 'react-intl'; + +interface IUploadButtonContainer { + composeId: string; +} + +const UploadButtonContainer: React.FC = ({ composeId }) => { + const dispatch = useAppDispatch(); + const { disabled, resetFileKey } = useAppSelector((state) => ({ + disabled: state.compose.get(composeId)?.is_uploading, + resetFileKey: state.compose.get(composeId)?.resetFileKey!, + })); + + const onSelectFile = (files: FileList, intl: IntlShape) => { + dispatch(uploadCompose(composeId, files, intl)); + }; + + return ; +}; + +export { UploadButtonContainer as default }; diff --git a/src/features/draft-statuses/components/draft-status-action-bar.tsx b/src/features/draft-statuses/components/draft-status-action-bar.tsx index 0ec6367b1..fc9e4bf77 100644 --- a/src/features/draft-statuses/components/draft-status-action-bar.tsx +++ b/src/features/draft-statuses/components/draft-status-action-bar.tsx @@ -35,7 +35,6 @@ const DraftStatusActionBar: React.FC = ({ source, status dispatch(cancelDraftStatus(source.draft_id)); } else { dispatch(openModal('CONFIRM', { - icon: require('@tabler/icons/outline/calendar-stats.svg'), heading: intl.formatMessage(messages.deleteHeading), message: intl.formatMessage(messages.deleteMessage), confirm: intl.formatMessage(messages.deleteConfirm), diff --git a/src/features/edit-profile/components/avatar-picker.tsx b/src/features/edit-profile/components/avatar-picker.tsx index 50dd9f0be..329e1d152 100644 --- a/src/features/edit-profile/components/avatar-picker.tsx +++ b/src/features/edit-profile/components/avatar-picker.tsx @@ -40,10 +40,10 @@ const AvatarPicker = React.forwardRef(({ dispatch(openModal('TEXT_FIELD', { heading: intl.formatMessage(messages.changeDescriptionHeading), - message: intl.formatMessage(messages.changeDescriptionPlaceholder), + placeholder: intl.formatMessage(messages.changeDescriptionPlaceholder), confirm: intl.formatMessage(messages.changeDescriptionConfirm), - onConfirm: (description: string) => { - onChangeDescription?.(description); + onConfirm: (description?: string) => { + onChangeDescription?.(description || ''); }, text: description, })); diff --git a/src/features/edit-profile/components/header-picker.tsx b/src/features/edit-profile/components/header-picker.tsx index 63b88f38f..37d805cf8 100644 --- a/src/features/edit-profile/components/header-picker.tsx +++ b/src/features/edit-profile/components/header-picker.tsx @@ -47,10 +47,10 @@ const HeaderPicker = React.forwardRef(({ dispatch(openModal('TEXT_FIELD', { heading: intl.formatMessage(messages.changeHeaderDescriptionHeading), - message: intl.formatMessage(messages.changeHeaderDescriptionPlaceholder), + placeholder: intl.formatMessage(messages.changeHeaderDescriptionPlaceholder), confirm: intl.formatMessage(messages.changeHeaderDescriptionConfirm), - onConfirm: (description: string) => { - onChangeDescription?.(description); + onConfirm: (description?: string) => { + onChangeDescription?.(description || ''); }, text: description, })); diff --git a/src/features/event/components/event-action-button.tsx b/src/features/event/components/event-action-button.tsx index 2c5acc6c7..73ffd2682 100644 --- a/src/features/event/components/event-action-button.tsx +++ b/src/features/event/components/event-action-button.tsx @@ -10,8 +10,9 @@ import type { ButtonThemes } from 'soapbox/components/ui/button/useButtonStyles' import type { Status as StatusEntity } from 'soapbox/normalizers'; const messages = defineMessages({ - leaveConfirm: { id: 'confirmations.leave_event.confirm', defaultMessage: 'Leave event' }, + leaveHeading: { id: 'confirmations.leave_event.heading', defaultMessage: 'Leave event' }, leaveMessage: { id: 'confirmations.leave_event.message', defaultMessage: 'If you want to rejoin the event, the request will be manually reviewed again. Are you sure you want to proceed?' }, + leaveConfirm: { id: 'confirmations.leave_event.confirm', defaultMessage: 'Leave event' }, }); interface IEventAction { @@ -44,6 +45,7 @@ const EventActionButton: React.FC = ({ status, theme = 'secondary' if (event.join_mode === 'restricted') { dispatch(openModal('CONFIRM', { + heading: intl.formatMessage(messages.leaveHeading), message: intl.formatMessage(messages.leaveMessage), confirm: intl.formatMessage(messages.leaveConfirm), onConfirm: () => dispatch(leaveEvent(status.id)), diff --git a/src/features/event/components/event-header.tsx b/src/features/event/components/event-header.tsx index e2a58bc22..ffcf6b16a 100644 --- a/src/features/event/components/event-header.tsx +++ b/src/features/event/components/event-header.tsx @@ -122,7 +122,7 @@ const EventHeader: React.FC = ({ status }) => { if (!boostModal) { modalReblog(); } else { - dispatch(openModal('BOOST', { status, onReblog: modalReblog })); + dispatch(openModal('BOOST', { statusId: status.id, onReblog: modalReblog })); } }; @@ -136,7 +136,6 @@ const EventHeader: React.FC = ({ status }) => { const handleDeleteClick = () => { dispatch(openModal('CONFIRM', { - icon: require('@tabler/icons/outline/trash.svg'), heading: intl.formatMessage(messages.deleteHeading), message: intl.formatMessage(messages.deleteMessage), confirm: intl.formatMessage(messages.deleteConfirm), @@ -164,7 +163,6 @@ const EventHeader: React.FC = ({ status }) => { const handleBlockClick = () => { dispatch(openModal('CONFIRM', { - icon: require('@tabler/icons/outline/ban.svg'), heading: , message: @{account.acct} }} />, confirm: intl.formatMessage(messages.blockConfirm), diff --git a/src/features/group/components/group-member-list-item.tsx b/src/features/group/components/group-member-list-item.tsx index 246dcd83e..09c5aae6c 100644 --- a/src/features/group/components/group-member-list-item.tsx +++ b/src/features/group/components/group-member-list-item.tsx @@ -30,8 +30,9 @@ const messages = defineMessages({ groupModDemote: { id: 'group.group_mod_demote', defaultMessage: 'Remove {role} role' }, groupModKick: { id: 'group.group_mod_kick', defaultMessage: 'Kick @{name} from group' }, groupModPromoteMod: { id: 'group.group_mod_promote_mod', defaultMessage: 'Assign {role} role' }, - kickConfirm: { id: 'confirmations.kick_from_group.confirm', defaultMessage: 'Kick' }, + kickFromGroupHeading: { id: 'confirmations.kick_from_group.heading', defaultMessage: 'Kick @{name}' }, kickFromGroupMessage: { id: 'confirmations.kick_from_group.message', defaultMessage: 'Are you sure you want to kick @{name} from this group?' }, + kickConfirm: { id: 'confirmations.kick_from_group.confirm', defaultMessage: 'Kick' }, kicked: { id: 'group.group_mod_kick.success', defaultMessage: 'Kicked @{name} from group' }, promoteConfirm: { id: 'group.promote.admin.confirmation.title', defaultMessage: 'Assign admin role' }, promoteConfirmMessage: { id: 'group.promote.admin.confirmation.message', defaultMessage: 'Are you sure you want to assign the admin role to @{name}?' }, @@ -66,6 +67,7 @@ const GroupMemberListItem = (props: IGroupMemberListItem) => { const handleKickFromGroup = () => { dispatch(openModal('CONFIRM', { + heading: intl.formatMessage(messages.kickFromGroupHeading, { name: account?.username }), message: intl.formatMessage(messages.kickFromGroupMessage, { name: account?.username }), confirm: intl.formatMessage(messages.kickConfirm), onConfirm: () => dispatch(groupKick(group.id, account?.id as string)).then(() => diff --git a/src/features/group/group-gallery.tsx b/src/features/group/group-gallery.tsx index 0944f95dc..3d8630626 100644 --- a/src/features/group/group-gallery.tsx +++ b/src/features/group/group-gallery.tsx @@ -39,7 +39,7 @@ const GroupGallery: React.FC = (props) => { const handleOpenMedia = (attachment: AccountGalleryAttachment) => { if (attachment.type === 'video') { - dispatch(openModal('VIDEO', { media: attachment, status: attachment.status, account: attachment.account })); + dispatch(openModal('VIDEO', { media: attachment, statusId: attachment.status.id, account: attachment.account })); } else { const media = (attachment.status as Status).media_attachments; const index = media.findIndex((x) => x.id === attachment.id); diff --git a/src/features/group/manage-group.tsx b/src/features/group/manage-group.tsx index a44fb070f..c2c5b8861 100644 --- a/src/features/group/manage-group.tsx +++ b/src/features/group/manage-group.tsx @@ -59,7 +59,6 @@ const ManageGroup: React.FC = ({ params }) => { const onDeleteGroup = () => dispatch(openModal('CONFIRM', { - icon: require('@tabler/icons/outline/trash.svg'), heading: intl.formatMessage(messages.deleteHeading), message: intl.formatMessage(messages.deleteMessage), confirm: intl.formatMessage(messages.deleteConfirm), diff --git a/src/features/notifications/components/notification.tsx b/src/features/notifications/components/notification.tsx index 0ce8d2f08..c24e66cc0 100644 --- a/src/features/notifications/components/notification.tsx +++ b/src/features/notifications/components/notification.tsx @@ -256,7 +256,7 @@ const Notification: React.FC = (props) => { if (e?.shiftKey || !boostModal) { dispatch(reblog(status)); } else { - dispatch(openModal('BOOST', { status, onReblog: (status: StatusEntity) => { + dispatch(openModal('BOOST', { statusId: status.id, onReblog: (status) => { dispatch(reblog(status)); } })); } diff --git a/src/features/scheduled-statuses/components/scheduled-status-action-bar.tsx b/src/features/scheduled-statuses/components/scheduled-status-action-bar.tsx index de9169ec3..d75b5fe9e 100644 --- a/src/features/scheduled-statuses/components/scheduled-status-action-bar.tsx +++ b/src/features/scheduled-statuses/components/scheduled-status-action-bar.tsx @@ -33,7 +33,6 @@ const ScheduledStatusActionBar: React.FC = ({ status dispatch(cancelScheduledStatus(status.id)); } else { dispatch(openModal('CONFIRM', { - icon: require('@tabler/icons/outline/calendar-stats.svg'), heading: intl.formatMessage(messages.deleteHeading), message: intl.formatMessage(messages.deleteMessage), confirm: intl.formatMessage(messages.deleteConfirm), diff --git a/src/features/status/components/status-interaction-bar.tsx b/src/features/status/components/status-interaction-bar.tsx index 497b6f07c..0b034eeb2 100644 --- a/src/features/status/components/status-interaction-bar.tsx +++ b/src/features/status/components/status-interaction-bar.tsx @@ -28,31 +28,19 @@ const StatusInteractionBar: React.FC = ({ status }): JSX. }; const onOpenReblogsModal = (username: string, statusId: string): void => { - dispatch(openModal('REBLOGS', { - username, - statusId, - })); + dispatch(openModal('REBLOGS', { statusId })); }; const onOpenFavouritesModal = (username: string, statusId: string): void => { - dispatch(openModal('FAVOURITES', { - username, - statusId, - })); + dispatch(openModal('FAVOURITES', { statusId })); }; const onOpenDislikesModal = (username: string, statusId: string): void => { - dispatch(openModal('DISLIKES', { - username, - statusId, - })); + dispatch(openModal('DISLIKES', { statusId })); }; const onOpenReactionsModal = (username: string, statusId: string): void => { - dispatch(openModal('REACTIONS', { - username, - statusId, - })); + dispatch(openModal('REACTIONS', { statusId })); }; const getNormalizedReacts = () => reduceEmoji( diff --git a/src/features/status/components/thread.tsx b/src/features/status/components/thread.tsx index 68485a26e..4c025b732 100644 --- a/src/features/status/components/thread.tsx +++ b/src/features/status/components/thread.tsx @@ -132,7 +132,7 @@ const Thread = (props: IThread) => { const handleReplyClick = (status: ComposeReplyAction['status']) => dispatch(replyCompose(status)); - const handleModalReblog = (status: SelectedStatus) => dispatch(reblog(status)); + const handleModalReblog = (status: Pick) => dispatch(reblog(status)); const handleReblogClick = (status: SelectedStatus, e?: React.MouseEvent) => { dispatch((_, getState) => { @@ -143,7 +143,7 @@ const Thread = (props: IThread) => { if ((e && e.shiftKey) || !boostModal) { handleModalReblog(status); } else { - dispatch(openModal('BOOST', { status, onReblog: handleModalReblog })); + dispatch(openModal('BOOST', { statusId: status.id, onReblog: handleModalReblog })); } } }); @@ -160,7 +160,7 @@ const Thread = (props: IThread) => { const firstAttachment = media[0]; if (media.length === 1 && firstAttachment.type === 'video') { - dispatch(openModal('VIDEO', { media: firstAttachment, status: status })); + dispatch(openModal('VIDEO', { media: firstAttachment, statusId: status.id })); } else { dispatch(openModal('MEDIA', { media, index: 0, statusId: status.id })); } diff --git a/src/features/ui/components/group-media-panel.tsx b/src/features/ui/components/group-media-panel.tsx index dd10bccbc..1869ec01c 100644 --- a/src/features/ui/components/group-media-panel.tsx +++ b/src/features/ui/components/group-media-panel.tsx @@ -28,7 +28,7 @@ const GroupMediaPanel: React.FC = ({ group }) => { const handleOpenMedia = (attachment: AccountGalleryAttachment): void => { if (attachment.type === 'video') { - dispatch(openModal('VIDEO', { media: attachment, status: attachment.status })); + dispatch(openModal('VIDEO', { media: attachment, statusId: attachment.status.id })); } else { const media = attachment.status.media_attachments; const index = media.findIndex(x => x.id === attachment.id); diff --git a/src/features/ui/components/modal-root.tsx b/src/features/ui/components/modal-root.tsx index 1b8046967..410dc609c 100644 --- a/src/features/ui/components/modal-root.tsx +++ b/src/features/ui/components/modal-root.tsx @@ -1,109 +1,92 @@ -import React, { Suspense } from 'react'; +import React, { Suspense, lazy } from 'react'; +import { cancelReplyCompose } from 'soapbox/actions/compose'; +import { cancelEventCompose } from 'soapbox/actions/events'; +import { closeModal } from 'soapbox/actions/modals'; +import { cancelReport } from 'soapbox/actions/reports'; import Base from 'soapbox/components/modal-root'; -import { - AccountModerationModal, - ActionsModal, - BirthdaysModal, - BoostModal, - CompareHistoryModal, - ComponentModal, - ComposeEventModal, - ComposeModal, - ConfirmationModal, - CryptoDonateModal, - DislikesModal, - EditAnnouncementModal, - EditBookmarkFolderModal, - EditDomainModal, - EditFederationModal, - EditRuleModal, - EmbedModal, - EventMapModal, - EventParticipantsModal, - FamiliarFollowersModal, - FavouritesModal, - HotkeysModal, - JoinEventModal, - LandingPageModal, - ListAdder, - ListEditor, - CreateGroupModal, - MediaModal, - MentionsModal, - MissingDescriptionModal, - MuteModal, - ReactionsModal, - ReblogsModal, - ReplyMentionsModal, - ReportModal, - SelectBookmarkFolderModal, - TextFieldModal, - UnauthorizedModal, - VideoModal, -} from 'soapbox/features/ui/util/async-components'; +import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; import ModalLoading from './modal-loading'; /* eslint sort-keys: "error" */ const MODAL_COMPONENTS = { - 'ACCOUNT_MODERATION': AccountModerationModal, - 'ACTIONS': ActionsModal, - 'BIRTHDAYS': BirthdaysModal, - 'BOOST': BoostModal, - 'COMPARE_HISTORY': CompareHistoryModal, - 'COMPONENT': ComponentModal, - 'COMPOSE': ComposeModal, - 'COMPOSE_EVENT': ComposeEventModal, - 'CONFIRM': ConfirmationModal, - 'CREATE_GROUP': CreateGroupModal, - 'CRYPTO_DONATE': CryptoDonateModal, - 'DISLIKES': DislikesModal, - 'EDIT_ANNOUNCEMENT': EditAnnouncementModal, - 'EDIT_BOOKMARK_FOLDER': EditBookmarkFolderModal, - 'EDIT_DOMAIN': EditDomainModal, - 'EDIT_FEDERATION': EditFederationModal, - 'EDIT_RULE': EditRuleModal, - 'EMBED': EmbedModal, - 'EVENT_MAP': EventMapModal, - 'EVENT_PARTICIPANTS': EventParticipantsModal, - 'FAMILIAR_FOLLOWERS': FamiliarFollowersModal, - 'FAVOURITES': FavouritesModal, - 'HOTKEYS': HotkeysModal, - 'JOIN_EVENT': JoinEventModal, - 'LANDING_PAGE': LandingPageModal, - 'LIST_ADDER': ListAdder, - 'LIST_EDITOR': ListEditor, - 'MEDIA': MediaModal, - 'MENTIONS': MentionsModal, - 'MISSING_DESCRIPTION': MissingDescriptionModal, - 'MUTE': MuteModal, - 'REACTIONS': ReactionsModal, - 'REBLOGS': ReblogsModal, - 'REPLY_MENTIONS': ReplyMentionsModal, - 'REPORT': ReportModal, - 'SELECT_BOOKMARK_FOLDER': SelectBookmarkFolderModal, - 'TEXT_FIELD': TextFieldModal, - 'UNAUTHORIZED': UnauthorizedModal, - 'VIDEO': VideoModal, + ACCOUNT_MODERATION: lazy(() => import('soapbox/features/ui/components/modals/account-moderation-modal/account-moderation-modal')), + BIRTHDAYS: lazy(() => import('soapbox/features/ui/components/modals/birthdays-modal')), + BOOST: lazy(() => import('soapbox/features/ui/components/modals/boost-modal')), + COMPARE_HISTORY: lazy(() => import('soapbox/features/ui/components/modals/compare-history-modal')), + COMPONENT: lazy(() => import('soapbox/features/ui/components/modals/component-modal')), + COMPOSE: lazy(() => import('soapbox/features/ui/components/modals/compose-modal')), + COMPOSE_EVENT: lazy(() => import('soapbox/features/ui/components/modals/compose-event-modal/compose-event-modal')), + CONFIRM: lazy(() => import('soapbox/features/ui/components/modals/confirmation-modal')), + CREATE_GROUP: lazy(() => import('soapbox/features/ui/components/modals/manage-group-modal/create-group-modal')), + CRYPTO_DONATE: lazy(() => import('soapbox/features/ui/components/modals/crypto-donate-modal')), + DISLIKES: lazy(() => import('soapbox/features/ui/components/modals/dislikes-modal')), + EDIT_ANNOUNCEMENT: lazy(() => import('soapbox/features/ui/components/modals/edit-announcement-modal')), + EDIT_BOOKMARK_FOLDER: lazy(() => import('soapbox/features/ui/components/modals/edit-bookmark-folder-modal')), + EDIT_DOMAIN: lazy(() => import('soapbox/features/ui/components/modals/edit-domain-modal')), + EDIT_FEDERATION: lazy(() => import('soapbox/features/ui/components/modals/edit-federation-modal')), + EDIT_RULE: lazy(() => import('soapbox/features/ui/components/modals/edit-rule-modal')), + EMBED: lazy(() => import('soapbox/features/ui/components/modals/embed-modal')), + EVENT_MAP: lazy(() => import('soapbox/features/ui/components/modals/event-map-modal')), + EVENT_PARTICIPANTS: lazy(() => import('soapbox/features/ui/components/modals/event-participants-modal')), + FAMILIAR_FOLLOWERS: lazy(() => import('soapbox/features/ui/components/modals/familiar-followers-modal')), + FAVOURITES: lazy(() => import('soapbox/features/ui/components/modals/favourites-modal')), + HOTKEYS: lazy(() => import('soapbox/features/ui/components/modals/hotkeys-modal')), + JOIN_EVENT: lazy(() => import('soapbox/features/ui/components/modals/join-event-modal')), + LIST_ADDER: lazy(() => import('soapbox/features/ui/components/modals/list-adder-modal')), + LIST_EDITOR: lazy(() => import('soapbox/features/ui/components/modals/list-editor-modal')), + MEDIA: lazy(() => import('soapbox/features/ui/components/modals/media-modal')), + MENTIONS: lazy(() => import('soapbox/features/ui/components/modals/mentions-modal')), + MISSING_DESCRIPTION: lazy(() => import('soapbox/features/ui/components/modals/missing-description-modal')), + MUTE: lazy(() => import('soapbox/features/ui/components/modals/mute-modal')), + REACTIONS: lazy(() => import('soapbox/features/ui/components/modals/reactions-modal')), + REBLOGS: lazy(() => import('soapbox/features/ui/components/modals/reblogs-modal')), + REPLY_MENTIONS: lazy(() => import('soapbox/features/ui/components/modals/reply-mentions-modal')), + REPORT: lazy(() => import('soapbox/features/ui/components/modals/report-modal/report-modal')), + SELECT_BOOKMARK_FOLDER: lazy(() => import('soapbox/features/ui/components/modals/select-bookmark-folder-modal')), + TEXT_FIELD: lazy(() => import('soapbox/features/ui/components/modals/text-field-modal')), + UNAUTHORIZED: lazy(() => import('soapbox/features/ui/components/modals/unauthorized-modal')), + VIDEO: lazy(() => import('soapbox/features/ui/components/modals/video-modal')), }; type ModalType = keyof typeof MODAL_COMPONENTS | null; -interface IModalRoot { - type: ModalType; - props?: Record | null; - onClose: (type?: ModalType) => void; -} +type BaseModalProps = { + /** Action to close the modal. */ + onClose(type?: ModalType): void; +}; -const ModalRoot: React.FC = ({ onClose, props, type }) => { - const renderLoading = (modalId: string) => !['MEDIA', 'VIDEO', 'BOOST', 'CONFIRM', 'ACTIONS'].includes(modalId) ? : null; +const ModalRoot: React.FC = () => { + const renderLoading = (modalId: string) => !['MEDIA', 'VIDEO', 'BOOST', 'CONFIRM'].includes(modalId) ? : null; - const onClickClose = (_?: ModalType) => { - onClose(type); + const dispatch = useAppDispatch(); + const { modalType: type, modalProps: props } = useAppSelector((state) => state.modals.last({ + modalProps: {}, + modalType: null, + })); + + const onClickClose = (type?: ModalType) => { + if (!type) return; + + switch (type) { + case 'COMPOSE': + dispatch(cancelReplyCompose()); + break; + case 'COMPOSE_EVENT': + dispatch(cancelEventCompose()); + break; + case 'REPORT': + dispatch(cancelReport()); + break; + default: + break; + } + + dispatch(closeModal(type)); }; - const Component = type ? (MODAL_COMPONENTS as Record>)[type] : null; + const Component = type !== null ? (MODAL_COMPONENTS as Record>)[type] : null; return ( @@ -116,4 +99,4 @@ const ModalRoot: React.FC = ({ onClose, props, type }) => { ); }; -export { type ModalType, ModalRoot as default }; +export { type BaseModalProps, type ModalType, ModalRoot as default }; diff --git a/src/features/ui/components/modals/account-moderation-modal/account-moderation-modal.tsx b/src/features/ui/components/modals/account-moderation-modal/account-moderation-modal.tsx index edf436615..92b47bae9 100644 --- a/src/features/ui/components/modals/account-moderation-modal/account-moderation-modal.tsx +++ b/src/features/ui/components/modals/account-moderation-modal/account-moderation-modal.tsx @@ -18,7 +18,7 @@ import { getBadges } from 'soapbox/utils/badges'; import BadgeInput from './badge-input'; import StaffRolePicker from './staff-role-picker'; -import type { ModalType } from '../../modal-root'; +import type { BaseModalProps } from '../../modal-root'; const messages = defineMessages({ userVerified: { id: 'admin.users.user_verified_message', defaultMessage: '@{acct} was verified' }, @@ -28,15 +28,13 @@ const messages = defineMessages({ badgesSaved: { id: 'admin.users.badges_saved_message', defaultMessage: 'Custom badges updated.' }, }); -interface IAccountModerationModal { - /** Action to close the modal. */ - onClose: (type: ModalType) => void; +interface AccountModerationModalProps { /** ID of the account to moderate. */ accountId: string; } /** Moderator actions against accounts. */ -const AccountModerationModal: React.FC = ({ onClose, accountId }) => { +const AccountModerationModal: React.FC = ({ onClose, accountId }) => { const intl = useIntl(); const dispatch = useAppDispatch(); @@ -183,4 +181,4 @@ const AccountModerationModal: React.FC = ({ onClose, ac ); }; -export { AccountModerationModal as default }; +export { type AccountModerationModalProps, AccountModerationModal as default }; diff --git a/src/features/ui/components/modals/actions-modal.tsx b/src/features/ui/components/modals/actions-modal.tsx deleted file mode 100644 index 856233f17..000000000 --- a/src/features/ui/components/modals/actions-modal.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import clsx from 'clsx'; -import React from 'react'; -import { FormattedMessage } from 'react-intl'; -import { spring } from 'react-motion'; -import { Link } from 'react-router-dom'; - -import Icon from 'soapbox/components/icon'; -import { HStack } from 'soapbox/components/ui'; - -import Motion from '../../util/optional-motion'; - -import type { Menu, MenuItem } from 'soapbox/components/dropdown-menu'; - -interface IActionsModal { - actions: Menu; - onClick: () => void; - onClose: () => void; -} - -const ActionsModal: React.FC = ({ actions, onClick, onClose }) => { - const renderAction = (action: MenuItem | null, i: number) => { - if (action === null) { - return
  • ; - } - - const { icon = null, text, meta = null, href = '#', to, destructive } = action; - - const Comp = href === '#' ? to ? Link : 'button' : 'a'; - const compProps = href === '#' ? to ? { to, onClick } : { onClick } : { href: href, rel: 'noopener', target: '_blank' }; - - return ( -
  • - - {icon && } -
    -
    {text}
    -
    {meta}
    -
    -
    -
  • - ); - }; - - return ( - - {({ top }) => ( -
    -
      - {actions && actions.map(renderAction)} - -
    • - -
    • - -
    • -
    -
    - )} -
    - ); -}; - -export { ActionsModal as default }; diff --git a/src/features/ui/components/modals/birthdays-modal.tsx b/src/features/ui/components/modals/birthdays-modal.tsx index 16e8637ec..8fbce550b 100644 --- a/src/features/ui/components/modals/birthdays-modal.tsx +++ b/src/features/ui/components/modals/birthdays-modal.tsx @@ -6,13 +6,9 @@ import { Modal, Spinner } from 'soapbox/components/ui'; import Account from 'soapbox/features/birthdays/account'; import { useAppSelector } from 'soapbox/hooks'; -import type { ModalType } from '../modal-root'; +import type { BaseModalProps } from '../modal-root'; -interface IBirthdaysModal { - onClose: (type: ModalType) => void; -} - -const BirthdaysModal = ({ onClose }: IBirthdaysModal) => { +const BirthdaysModal = ({ onClose }: BaseModalProps) => { const accountIds = useAppSelector(state => state.user_lists.birthday_reminders.get(state.me as string)?.items); const onClickClose = () => { diff --git a/src/features/ui/components/modals/boost-modal.tsx b/src/features/ui/components/modals/boost-modal.tsx index 127713317..e429a697c 100644 --- a/src/features/ui/components/modals/boost-modal.tsx +++ b/src/features/ui/components/modals/boost-modal.tsx @@ -1,10 +1,13 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import Icon from 'soapbox/components/icon'; import { Modal, Stack, Text } from 'soapbox/components/ui'; import ReplyIndicator from 'soapbox/features/compose/components/reply-indicator'; +import { useAppSelector } from 'soapbox/hooks'; +import { makeGetStatus } from 'soapbox/selectors'; +import type { BaseModalProps } from '../modal-root'; import type { Status as StatusEntity } from 'soapbox/normalizers'; const messages = defineMessages({ @@ -12,14 +15,16 @@ const messages = defineMessages({ reblog: { id: 'status.reblog', defaultMessage: 'Repost' }, }); -interface IBoostModal { - status: StatusEntity; +interface BoostModalProps { + statusId: string; onReblog: (status: Pick) => void; - onClose: () => void; } -const BoostModal: React.FC = ({ status, onReblog, onClose }) => { +const BoostModal: React.FC = ({ statusId, onReblog, onClose }) => { + const getStatus = useCallback(makeGetStatus(), []); + const intl = useIntl(); + const status = useAppSelector(state => getStatus(state, { id: statusId }))!; const handleReblog = () => { onReblog(status); @@ -45,4 +50,4 @@ const BoostModal: React.FC = ({ status, onReblog, onClose }) => { ); }; -export { BoostModal as default }; +export { type BoostModalProps, BoostModal as default }; diff --git a/src/features/ui/components/modals/compare-history-modal.tsx b/src/features/ui/components/modals/compare-history-modal.tsx index 44380e3ee..6f14ff681 100644 --- a/src/features/ui/components/modals/compare-history-modal.tsx +++ b/src/features/ui/components/modals/compare-history-modal.tsx @@ -6,14 +6,13 @@ import AttachmentThumbs from 'soapbox/components/attachment-thumbs'; import { HStack, Modal, Spinner, Stack, Text } from 'soapbox/components/ui'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; -import type { ModalType } from '../modal-root'; +import type { BaseModalProps } from '../modal-root'; -interface ICompareHistoryModal { - onClose: (type: ModalType) => void; +interface CompareHistoryModalProps { statusId: string; } -const CompareHistoryModal: React.FC = ({ onClose, statusId }) => { +const CompareHistoryModal: React.FC = ({ onClose, statusId }) => { const dispatch = useAppDispatch(); const loading = useAppSelector(state => state.history.getIn([statusId, 'loading'])); @@ -93,4 +92,4 @@ const CompareHistoryModal: React.FC = ({ onClose, statusId ); }; -export { CompareHistoryModal as default }; +export { type CompareHistoryModalProps, CompareHistoryModal as default }; diff --git a/src/features/ui/components/modals/component-modal.tsx b/src/features/ui/components/modals/component-modal.tsx index 3b7a17345..951361e62 100644 --- a/src/features/ui/components/modals/component-modal.tsx +++ b/src/features/ui/components/modals/component-modal.tsx @@ -2,20 +2,17 @@ import React from 'react'; import { Modal } from 'soapbox/components/ui'; -import type { ModalType } from '../modal-root'; +import type { BaseModalProps } from '../modal-root'; -interface IComponentModal { - onClose: (type?: ModalType) => void; - component: React.ComponentType<{ - onClose: (type?: ModalType) => void; - }>; +interface ComponentModalProps { + component: React.ComponentType; componentProps: Record; } -const ComponentModal: React.FC = ({ onClose, component: Component, componentProps = {} }) => ( +const ComponentModal: React.FC = ({ onClose, component: Component, componentProps = {} }) => ( ); -export { ComponentModal as default }; +export { ComponentModal as default, type ComponentModalProps }; diff --git a/src/features/ui/components/modals/compose-event-modal/compose-event-modal.tsx b/src/features/ui/components/modals/compose-event-modal/compose-event-modal.tsx index ac6839bd4..824860c41 100644 --- a/src/features/ui/components/modals/compose-event-modal/compose-event-modal.tsx +++ b/src/features/ui/components/modals/compose-event-modal/compose-event-modal.tsx @@ -29,7 +29,7 @@ import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; import UploadButton from './upload-button'; -import type { ModalType } from '../../modal-root'; +import type { BaseModalProps } from '../../modal-root'; const messages = defineMessages({ eventNamePlaceholder: { id: 'compose_event.fields.name_placeholder', defaultMessage: 'Name' }, @@ -87,11 +87,7 @@ const Account: React.FC = ({ eventId, id, participationMessage }) => { ); }; -interface IComposeEventModal { - onClose: (type?: ModalType) => void; -} - -const ComposeEventModal: React.FC = ({ onClose }) => { +const ComposeEventModal: React.FC = ({ onClose }) => { const intl = useIntl(); const dispatch = useAppDispatch(); @@ -139,7 +135,6 @@ const ComposeEventModal: React.FC = ({ onClose }) => { dispatch((dispatch, getState) => { if (checkEventComposeContent(getState().compose_event)) { dispatch(openModal('CONFIRM', { - icon: require('@tabler/icons/outline/trash.svg'), heading: statusId ? : , diff --git a/src/features/ui/components/modals/compose-modal.tsx b/src/features/ui/components/modals/compose-modal.tsx index f21dc0426..94c1bbb40 100644 --- a/src/features/ui/components/modals/compose-modal.tsx +++ b/src/features/ui/components/modals/compose-modal.tsx @@ -11,7 +11,7 @@ import { useAppDispatch, useCompose, useDraggedFiles } from 'soapbox/hooks'; import ComposeForm from '../../../compose/components/compose-form'; -import type { ModalType } from '../modal-root'; +import type { BaseModalProps } from '../modal-root'; const messages = defineMessages({ confirm: { id: 'confirmations.cancel.confirm', defaultMessage: 'Discard' }, @@ -19,12 +19,11 @@ const messages = defineMessages({ saveDraft: { id: 'confirmations.cancel_editing.save_draft', defaultMessage: 'Save draft' }, }); -interface IComposeModal { - onClose: (type?: ModalType) => void; +interface ComposeModalProps { composeId?: string; } -const ComposeModal: React.FC = ({ onClose, composeId = 'compose-modal' }) => { +const ComposeModal: React.FC = ({ onClose, composeId = 'compose-modal' }) => { const intl = useIntl(); const dispatch = useAppDispatch(); const node = useRef(null); @@ -39,7 +38,6 @@ const ComposeModal: React.FC = ({ onClose, composeId = 'compose-m const onClickClose = () => { if (checkComposeContent(compose)) { dispatch(openModal('CONFIRM', { - icon: require('@tabler/icons/outline/trash.svg'), heading: statusId ? : , @@ -96,4 +94,4 @@ const ComposeModal: React.FC = ({ onClose, composeId = 'compose-m ); }; -export { ComposeModal as default }; +export { type ComposeModalProps, ComposeModal as default }; diff --git a/src/features/ui/components/modals/confirmation-modal.tsx b/src/features/ui/components/modals/confirmation-modal.tsx index 86b0f2a9e..0c5c4590c 100644 --- a/src/features/ui/components/modals/confirmation-modal.tsx +++ b/src/features/ui/components/modals/confirmation-modal.tsx @@ -4,23 +4,22 @@ import { FormattedMessage } from 'react-intl'; import List, { ListItem } from 'soapbox/components/list'; import { Modal, Stack, Text, Toggle } from 'soapbox/components/ui'; -import type { ModalType } from '../modal-root'; +import type { BaseModalProps } from '../modal-root'; import type { ButtonThemes } from 'soapbox/components/ui/button/useButtonStyles'; -interface IConfirmationModal { - heading: React.ReactNode; +interface ConfirmationModalProps { + heading?: React.ReactNode; message: React.ReactNode; confirm: React.ReactNode; - onClose: (type: ModalType) => void; onConfirm: () => void; - secondary: React.ReactNode; + secondary?: React.ReactNode; onSecondary?: () => void; - onCancel: () => void; - checkbox?: JSX.Element; + onCancel?: () => void; + checkbox?: string | false; confirmationTheme?: ButtonThemes; } -const ConfirmationModal: React.FC = ({ +const ConfirmationModal: React.FC = ({ heading, message, confirm, @@ -58,7 +57,7 @@ const ConfirmationModal: React.FC = ({ title={heading} confirmationAction={handleClick} confirmationText={confirm} - confirmationDisabled={checkbox && !checked} + confirmationDisabled={!!checkbox && !checked} confirmationTheme={confirmationTheme} cancelText={} cancelAction={handleCancel} @@ -86,4 +85,4 @@ const ConfirmationModal: React.FC = ({ ); }; -export { ConfirmationModal as default }; +export { ConfirmationModal as default, type ConfirmationModalProps }; diff --git a/src/features/ui/components/modals/crypto-donate-modal.tsx b/src/features/ui/components/modals/crypto-donate-modal.tsx index b665fe375..c57b67ae4 100644 --- a/src/features/ui/components/modals/crypto-donate-modal.tsx +++ b/src/features/ui/components/modals/crypto-donate-modal.tsx @@ -3,9 +3,11 @@ import React from 'react'; import { Modal } from 'soapbox/components/ui'; import DetailedCryptoAddress from 'soapbox/features/crypto-donate/components/detailed-crypto-address'; +import { BaseModalProps } from '../modal-root'; + import type { ICryptoAddress } from '../../../crypto-donate/components/crypto-address'; -const CryptoDonateModal: React.FC void }> = ({ onClose, ...props }) => { +const CryptoDonateModal: React.FC = ({ onClose, ...props }) => { return ( diff --git a/src/features/ui/components/modals/dislikes-modal.tsx b/src/features/ui/components/modals/dislikes-modal.tsx index db5976aa2..08d5d0e24 100644 --- a/src/features/ui/components/modals/dislikes-modal.tsx +++ b/src/features/ui/components/modals/dislikes-modal.tsx @@ -7,14 +7,13 @@ import { Modal, Spinner } from 'soapbox/components/ui'; import AccountContainer from 'soapbox/containers/account-container'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; -import type { ModalType } from '../modal-root'; +import type { BaseModalProps } from '../modal-root'; -interface IDislikesModal { - onClose: (type: ModalType) => void; +interface DislikesModalProps { statusId: string; } -const DislikesModal: React.FC = ({ onClose, statusId }) => { +const DislikesModal: React.FC = ({ onClose, statusId }) => { const dispatch = useAppDispatch(); const accountIds = useAppSelector((state) => state.user_lists.disliked_by.get(statusId)?.items); @@ -62,4 +61,4 @@ const DislikesModal: React.FC = ({ onClose, statusId }) => { ); }; -export { DislikesModal as default }; +export { DislikesModal as default, type DislikesModalProps }; diff --git a/src/features/ui/components/modals/edit-announcement-modal.tsx b/src/features/ui/components/modals/edit-announcement-modal.tsx index 8ccc5a3d0..1ee36e660 100644 --- a/src/features/ui/components/modals/edit-announcement-modal.tsx +++ b/src/features/ui/components/modals/edit-announcement-modal.tsx @@ -8,7 +8,7 @@ import { DatePicker } from 'soapbox/features/ui/util/async-components'; import { useAppDispatch } from 'soapbox/hooks'; import toast from 'soapbox/toast'; -import type { ModalType } from '../modal-root'; +import type { BaseModalProps } from '../modal-root'; import type { AdminAnnouncement } from 'soapbox/schemas'; const messages = defineMessages({ @@ -20,12 +20,11 @@ const messages = defineMessages({ announcementUpdateSuccess: { id: 'admin.edit_announcement.updated', defaultMessage: 'Announcement edited' }, }); -interface IEditAnnouncementModal { - onClose: (type?: ModalType) => void; +interface EditAnnouncementModalProps { announcement?: AdminAnnouncement; } -const EditAnnouncementModal: React.FC = ({ onClose, announcement }) => { +const EditAnnouncementModal: React.FC = ({ onClose, announcement }) => { const dispatch = useAppDispatch(); const { createAnnouncement, updateAnnouncement } = useAnnouncements(); const intl = useIntl(); @@ -139,4 +138,4 @@ const EditAnnouncementModal: React.FC = ({ onClose, anno ); }; -export { EditAnnouncementModal as default }; +export { EditAnnouncementModal as default, type EditAnnouncementModalProps }; diff --git a/src/features/ui/components/modals/edit-bookmark-folder-modal.tsx b/src/features/ui/components/modals/edit-bookmark-folder-modal.tsx index 023554632..9802240fc 100644 --- a/src/features/ui/components/modals/edit-bookmark-folder-modal.tsx +++ b/src/features/ui/components/modals/edit-bookmark-folder-modal.tsx @@ -12,7 +12,7 @@ import { useAppDispatch, useClickOutside } from 'soapbox/hooks'; import { useTextField } from 'soapbox/hooks/forms'; import toast from 'soapbox/toast'; -import type { ModalType } from '../modal-root'; +import type { BaseModalProps } from '../modal-root'; import type { Emoji as EmojiType } from 'soapbox/features/emoji'; const messages = defineMessages({ @@ -86,12 +86,11 @@ const EmojiPicker: React.FC = ({ emoji, emojiUrl, ...props }) => { ); }; -interface IEditBookmarkFolderModal { +interface EditBookmarkFolderModalProps { folderId: string; - onClose: (type: ModalType) => void; } -const EditBookmarkFolderModal: React.FC = ({ folderId, onClose }) => { +const EditBookmarkFolderModal: React.FC = ({ folderId, onClose }) => { const intl = useIntl(); const dispatch = useAppDispatch(); @@ -160,4 +159,4 @@ const EditBookmarkFolderModal: React.FC = ({ folderId, ); }; -export { EditBookmarkFolderModal as default }; +export { EditBookmarkFolderModal as default, type EditBookmarkFolderModalProps }; diff --git a/src/features/ui/components/modals/edit-domain-modal.tsx b/src/features/ui/components/modals/edit-domain-modal.tsx index 7f2ac77f5..ea7e39111 100644 --- a/src/features/ui/components/modals/edit-domain-modal.tsx +++ b/src/features/ui/components/modals/edit-domain-modal.tsx @@ -7,8 +7,8 @@ import { Form, FormGroup, HStack, Input, Modal, Stack, Text, Toggle } from 'soap import { useAppDispatch } from 'soapbox/hooks'; import toast from 'soapbox/toast'; -import type { ModalType } from '../modal-root'; -import type{ Domain } from 'soapbox/schemas'; +import type { BaseModalProps } from '../modal-root'; +import type { Domain } from 'soapbox/schemas'; const messages = defineMessages({ save: { id: 'admin.edit_domain.save', defaultMessage: 'Save' }, @@ -17,12 +17,11 @@ const messages = defineMessages({ domainUpdateSuccess: { id: 'admin.edit_domain.updated', defaultMessage: 'Domain edited' }, }); -interface IEditDomainModal { - onClose: (type?: ModalType) => void; +interface EditDomainModalProps { domainId?: string; } -const EditDomainModal: React.FC = ({ onClose, domainId }) => { +const EditDomainModal: React.FC = ({ onClose, domainId }) => { const dispatch = useAppDispatch(); const intl = useIntl(); @@ -101,4 +100,4 @@ const EditDomainModal: React.FC = ({ onClose, domainId }) => { ); }; -export { EditDomainModal as default }; +export { EditDomainModal as default, type EditDomainModalProps }; diff --git a/src/features/ui/components/modals/edit-federation-modal.tsx b/src/features/ui/components/modals/edit-federation-modal.tsx index 77a8a6950..7d0a0541d 100644 --- a/src/features/ui/components/modals/edit-federation-modal.tsx +++ b/src/features/ui/components/modals/edit-federation-modal.tsx @@ -8,6 +8,8 @@ import { useAppSelector, useAppDispatch } from 'soapbox/hooks'; import { makeGetRemoteInstance } from 'soapbox/selectors'; import toast from 'soapbox/toast'; +import type { BaseModalProps } from '../modal-root'; + const messages = defineMessages({ mediaRemoval: { id: 'edit_federation.media_removal', defaultMessage: 'Strip media' }, forceNsfw: { id: 'edit_federation.force_nsfw', defaultMessage: 'Force attachments to be marked sensitive' }, @@ -17,13 +19,12 @@ const messages = defineMessages({ success: { id: 'edit_federation.success', defaultMessage: '{host} federation was updated' }, }); -interface IEditFederationModal { +interface EditFederationModalProps { host: string; - onClose: () => void; } /** Modal for moderators to edit federation with a remote instance. */ -const EditFederationModal: React.FC = ({ host, onClose }) => { +const EditFederationModal: React.FC = ({ host, onClose }) => { const intl = useIntl(); const dispatch = useAppDispatch(); @@ -127,4 +128,4 @@ const EditFederationModal: React.FC = ({ host, onClose }) ); }; -export { EditFederationModal as default }; +export { EditFederationModal as default, type EditFederationModalProps }; diff --git a/src/features/ui/components/modals/edit-rule-modal.tsx b/src/features/ui/components/modals/edit-rule-modal.tsx index 22465b468..a41df3f63 100644 --- a/src/features/ui/components/modals/edit-rule-modal.tsx +++ b/src/features/ui/components/modals/edit-rule-modal.tsx @@ -6,8 +6,8 @@ import { Form, FormGroup, Input, Modal } from 'soapbox/components/ui'; import { useTextField } from 'soapbox/hooks/forms'; import toast from 'soapbox/toast'; -import type { ModalType } from '../modal-root'; -import type{ AdminRule } from 'soapbox/schemas'; +import type { BaseModalProps } from '../modal-root'; +import type { AdminRule } from 'soapbox/schemas'; const messages = defineMessages({ save: { id: 'admin.edit_rule.save', defaultMessage: 'Save' }, @@ -17,12 +17,11 @@ const messages = defineMessages({ ruleUpdateSuccess: { id: 'admin.edit_rule.updated', defaultMessage: 'Rule edited' }, }); -interface IEditRuleModal { - onClose: (type?: ModalType) => void; +interface EditRuleModalProps { rule?: AdminRule; } -const EditRuleModal: React.FC = ({ onClose, rule }) => { +const EditRuleModal: React.FC = ({ onClose, rule }) => { const intl = useIntl(); const { createRule, updateRule } = useRules(); @@ -91,4 +90,4 @@ const EditRuleModal: React.FC = ({ onClose, rule }) => { ); }; -export { EditRuleModal as default }; +export { EditRuleModal as default, type EditRuleModalProps }; diff --git a/src/features/ui/components/modals/embed-modal.tsx b/src/features/ui/components/modals/embed-modal.tsx index 1f536d74d..0bd1dfc4e 100644 --- a/src/features/ui/components/modals/embed-modal.tsx +++ b/src/features/ui/components/modals/embed-modal.tsx @@ -8,12 +8,14 @@ import { Modal, Stack, Text, Divider } from 'soapbox/components/ui'; import { useAppDispatch } from 'soapbox/hooks'; import useEmbed from 'soapbox/queries/embed'; -interface IEmbedModal { +import type { BaseModalProps } from '../modal-root'; + +interface EmbedModalProps { url: string; onError: (error: any) => void; } -const EmbedModal: React.FC = ({ url, onError }) => { +const EmbedModal: React.FC = ({ url, onError }) => { const dispatch = useAppDispatch(); const { data: embed, error, isError } = useEmbed(url); @@ -54,4 +56,4 @@ const EmbedModal: React.FC = ({ url, onError }) => { ); }; -export { EmbedModal as default }; +export { EmbedModal as default, type EmbedModalProps }; diff --git a/src/features/ui/components/modals/event-map-modal.tsx b/src/features/ui/components/modals/event-map-modal.tsx index ef6055c0e..92e58fed0 100644 --- a/src/features/ui/components/modals/event-map-modal.tsx +++ b/src/features/ui/components/modals/event-map-modal.tsx @@ -8,7 +8,7 @@ import { makeGetStatus } from 'soapbox/selectors'; import 'leaflet/dist/leaflet.css'; -import type { ModalType } from '../modal-root'; +import type { BaseModalProps } from '../modal-root'; L.Icon.Default.mergeOptions({ iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'), @@ -16,12 +16,11 @@ L.Icon.Default.mergeOptions({ shadowUrl: require('leaflet/dist/images/marker-shadow.png'), }); -interface IEventMapModal { - onClose: (type: ModalType) => void; +interface EventMapModalProps { statusId: string; } -const EventMapModal: React.FC = ({ onClose, statusId }) => { +const EventMapModal: React.FC = ({ onClose, statusId }) => { const { tileServer, tileServerAttribution } = useSoapboxConfig(); const getStatus = useCallback(makeGetStatus(), []); @@ -72,4 +71,4 @@ const EventMapModal: React.FC = ({ onClose, statusId }) => { ); }; -export { EventMapModal as default }; +export { EventMapModal as default, type EventMapModalProps }; diff --git a/src/features/ui/components/modals/event-participants-modal.tsx b/src/features/ui/components/modals/event-participants-modal.tsx index 1272351b0..1a02ed199 100644 --- a/src/features/ui/components/modals/event-participants-modal.tsx +++ b/src/features/ui/components/modals/event-participants-modal.tsx @@ -7,14 +7,13 @@ import { Modal, Spinner } from 'soapbox/components/ui'; import AccountContainer from 'soapbox/containers/account-container'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; -import type { ModalType } from '../modal-root'; +import type { BaseModalProps } from '../modal-root'; -interface IEventParticipantsModal { - onClose: (type: ModalType) => void; +interface EventParticipantsModalProps { statusId: string; } -const EventParticipantsModal: React.FC = ({ onClose, statusId }) => { +const EventParticipantsModal: React.FC = ({ onClose, statusId }) => { const dispatch = useAppDispatch(); const accountIds = useAppSelector((state) => state.user_lists.event_participations.get(statusId)?.items); @@ -62,4 +61,4 @@ const EventParticipantsModal: React.FC = ({ onClose, st ); }; -export { EventParticipantsModal as default }; +export { EventParticipantsModal as default, type EventParticipantsModalProps }; diff --git a/src/features/ui/components/modals/familiar-followers-modal.tsx b/src/features/ui/components/modals/familiar-followers-modal.tsx index 57e41a442..3e00d62ac 100644 --- a/src/features/ui/components/modals/familiar-followers-modal.tsx +++ b/src/features/ui/components/modals/familiar-followers-modal.tsx @@ -8,16 +8,15 @@ import AccountContainer from 'soapbox/containers/account-container'; import { useAppSelector } from 'soapbox/hooks'; import { makeGetAccount } from 'soapbox/selectors'; -import type { ModalType } from '../modal-root'; +import type { BaseModalProps } from '../modal-root'; const getAccount = makeGetAccount(); -interface IFamiliarFollowersModal { +interface FamiliarFollowersModalProps { accountId: string; - onClose: (type: ModalType) => void; } -const FamiliarFollowersModal = ({ accountId, onClose }: IFamiliarFollowersModal) => { +const FamiliarFollowersModal: React.FC = ({ accountId, onClose }) => { const account = useAppSelector(state => getAccount(state, accountId)); const familiarFollowerIds: ImmutableOrderedSet = useAppSelector(state => state.user_lists.familiar_followers.get(accountId)?.items || ImmutableOrderedSet()); @@ -63,4 +62,4 @@ const FamiliarFollowersModal = ({ accountId, onClose }: IFamiliarFollowersModal) ); }; -export { FamiliarFollowersModal as default }; +export { FamiliarFollowersModal as default, type FamiliarFollowersModalProps }; diff --git a/src/features/ui/components/modals/favourites-modal.tsx b/src/features/ui/components/modals/favourites-modal.tsx index 520f8e5a8..d777477fe 100644 --- a/src/features/ui/components/modals/favourites-modal.tsx +++ b/src/features/ui/components/modals/favourites-modal.tsx @@ -7,14 +7,13 @@ import { Modal, Spinner } from 'soapbox/components/ui'; import AccountContainer from 'soapbox/containers/account-container'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; -import type { ModalType } from '../modal-root'; +import type { BaseModalProps } from '../modal-root'; -interface IFavouritesModal { - onClose: (type: ModalType) => void; +interface FavouritesModalProps { statusId: string; } -const FavouritesModal: React.FC = ({ onClose, statusId }) => { +const FavouritesModal: React.FC = ({ onClose, statusId }) => { const dispatch = useAppDispatch(); const accountIds = useAppSelector((state) => state.user_lists.favourited_by.get(statusId)?.items); @@ -73,4 +72,4 @@ const FavouritesModal: React.FC = ({ onClose, statusId }) => { ); }; -export { FavouritesModal as default }; +export { FavouritesModal as default, type FavouritesModalProps }; diff --git a/src/features/ui/components/modals/hotkeys-modal.tsx b/src/features/ui/components/modals/hotkeys-modal.tsx index 7d1545549..52ca57f3c 100644 --- a/src/features/ui/components/modals/hotkeys-modal.tsx +++ b/src/features/ui/components/modals/hotkeys-modal.tsx @@ -4,9 +4,7 @@ import { FormattedMessage } from 'react-intl'; import { Modal } from 'soapbox/components/ui'; import { useFeatures } from 'soapbox/hooks'; -interface IHotkeysModal { - onClose: () => void; -} +import type { BaseModalProps } from '../modal-root'; const Hotkey: React.FC<{ children: React.ReactNode }> = ({ children }) => ( @@ -20,7 +18,7 @@ const TableCell: React.FC<{ children: React.ReactNode }> = ({ children }) => ( ); -const HotkeysModal: React.FC = ({ onClose }) => { +const HotkeysModal: React.FC = ({ onClose }) => { const features = useFeatures(); return ( diff --git a/src/features/ui/components/modals/join-event-modal.tsx b/src/features/ui/components/modals/join-event-modal.tsx index 77cc2a41b..3cf083ec8 100644 --- a/src/features/ui/components/modals/join-event-modal.tsx +++ b/src/features/ui/components/modals/join-event-modal.tsx @@ -6,17 +6,19 @@ import { closeModal } from 'soapbox/actions/modals'; import { FormGroup, Modal, Textarea } from 'soapbox/components/ui'; import { useAppDispatch } from 'soapbox/hooks'; +import { BaseModalProps } from '../modal-root'; + const messages = defineMessages({ hint: { id: 'join_event.hint', defaultMessage: 'You can tell the organizer why do you want to participate in this event:' }, placeholder: { id: 'join_event.placeholder', defaultMessage: 'Message to organizer' }, join: { id: 'join_event.join', defaultMessage: 'Request join' }, }); -interface IJoinEventModal { +interface JoinEventModalProps { statusId: string; } -const JoinEventModal: React.FC = ({ statusId }) => { +const JoinEventModal: React.FC = ({ statusId }) => { const intl = useIntl(); const dispatch = useAppDispatch(); @@ -66,4 +68,4 @@ const JoinEventModal: React.FC = ({ statusId }) => { ); }; -export { JoinEventModal as default }; +export { JoinEventModal as default, type JoinEventModalProps }; diff --git a/src/features/ui/components/modals/landing-page-modal.test.tsx b/src/features/ui/components/modals/landing-page-modal.test.tsx deleted file mode 100644 index bb50a4c9b..000000000 --- a/src/features/ui/components/modals/landing-page-modal.test.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; - -import { storeOpen } from 'soapbox/jest/mock-stores'; -import { render, screen } from 'soapbox/jest/test-helpers'; - -import LandingPageModal from './landing-page-modal'; - -describe('', () => { - it('successfully renders', () => { - render(); - expect(screen.getByTestId('modal')).toBeInTheDocument(); - }); - - it('doesn\'t display the signup button by default', () => { - render(); - expect(screen.queryByText('Register')).not.toBeInTheDocument(); - }); - - describe('with registrations enabled', () => { - it('displays the signup button', () => { - render(, undefined, storeOpen); - expect(screen.getByText('Register')).toBeInTheDocument(); - }); - }); -}); diff --git a/src/features/ui/components/modals/landing-page-modal.tsx b/src/features/ui/components/modals/landing-page-modal.tsx deleted file mode 100644 index b5c7a48ca..000000000 --- a/src/features/ui/components/modals/landing-page-modal.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import clsx from 'clsx'; -import React from 'react'; -import { defineMessages, useIntl } from 'react-intl'; - -import SiteLogo from 'soapbox/components/site-logo'; -import { Text, Button, Icon, Modal } from 'soapbox/components/ui'; -import { useRegistrationStatus, useSoapboxConfig } from 'soapbox/hooks'; - -import type { ModalType } from '../modal-root'; - -const messages = defineMessages({ - helpCenter: { id: 'landing_page_modal.help_center', defaultMessage: 'Help Center' }, - login: { id: 'header.login.label', defaultMessage: 'Log in' }, - register: { id: 'header.register.label', defaultMessage: 'Register' }, -}); - -interface ILandingPageModal { - onClose: (type: ModalType) => void; -} - -/** Login and links to display from the hamburger menu of the homepage. */ -const LandingPageModal: React.FC = ({ onClose }) => { - const intl = useIntl(); - - const soapboxConfig = useSoapboxConfig(); - const { isOpen } = useRegistrationStatus(); - const { links } = soapboxConfig; - - return ( - } - onClose={() => onClose('LANDING_PAGE')} - > -
    - {links.get('help') && ( - - )} - -
    - - - {isOpen && ( - - )} -
    -
    -
    - ); -}; - -export { LandingPageModal as default }; diff --git a/src/features/list-adder/components/list.tsx b/src/features/ui/components/modals/list-adder-modal/components/list.tsx similarity index 100% rename from src/features/list-adder/components/list.tsx rename to src/features/ui/components/modals/list-adder-modal/components/list.tsx diff --git a/src/features/list-adder/index.tsx b/src/features/ui/components/modals/list-adder-modal/index.tsx similarity index 85% rename from src/features/list-adder/index.tsx rename to src/features/ui/components/modals/list-adder-modal/index.tsx index 4874034a8..89edbdc9e 100644 --- a/src/features/list-adder/index.tsx +++ b/src/features/ui/components/modals/list-adder-modal/index.tsx @@ -5,15 +5,14 @@ import { createSelector } from 'reselect'; import { setupListAdder, resetListAdder } from 'soapbox/actions/lists'; import { CardHeader, CardTitle, Modal } from 'soapbox/components/ui'; import AccountContainer from 'soapbox/containers/account-container'; +import NewListForm from 'soapbox/features/lists/components/new-list-form'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; -import NewListForm from '../lists/components/new-list-form'; - import List from './components/list'; -import type { ModalType } from '../ui/components/modal-root'; import type { List as ImmutableList } from 'immutable'; import type { List as ListEntity } from 'pl-api'; +import type { BaseModalProps } from 'soapbox/features/ui/components/modal-root'; import type { RootState } from 'soapbox/store'; const messages = defineMessages({ @@ -30,12 +29,11 @@ const getOrderedLists = createSelector([(state: RootState) => state.lists], list return lists.toList().filter(item => !!item).sort((a, b) => (a as ListEntity).title.localeCompare((b as ListEntity).title)) as ImmutableList; }); -interface IListAdder { +interface ListAdderModalProps { accountId: string; - onClose: (type: ModalType) => void; } -const ListAdder: React.FC = ({ accountId, onClose }) => { +const ListAdderModal: React.FC = ({ accountId, onClose }) => { const intl = useIntl(); const dispatch = useAppDispatch(); @@ -79,4 +77,4 @@ const ListAdder: React.FC = ({ accountId, onClose }) => { ); }; -export { ListAdder as default }; +export { type ListAdderModalProps, ListAdderModal as default }; diff --git a/src/features/list-editor/components/account.tsx b/src/features/ui/components/modals/list-editor-modal/components/account.tsx similarity index 100% rename from src/features/list-editor/components/account.tsx rename to src/features/ui/components/modals/list-editor-modal/components/account.tsx diff --git a/src/features/list-editor/components/edit-list-form.tsx b/src/features/ui/components/modals/list-editor-modal/components/edit-list-form.tsx similarity index 100% rename from src/features/list-editor/components/edit-list-form.tsx rename to src/features/ui/components/modals/list-editor-modal/components/edit-list-form.tsx diff --git a/src/features/list-editor/components/search.tsx b/src/features/ui/components/modals/list-editor-modal/components/search.tsx similarity index 100% rename from src/features/list-editor/components/search.tsx rename to src/features/ui/components/modals/list-editor-modal/components/search.tsx diff --git a/src/features/list-editor/index.tsx b/src/features/ui/components/modals/list-editor-modal/index.tsx similarity index 89% rename from src/features/list-editor/index.tsx rename to src/features/ui/components/modals/list-editor-modal/index.tsx index ece178ce3..0bda12e9a 100644 --- a/src/features/list-editor/index.tsx +++ b/src/features/ui/components/modals/list-editor-modal/index.tsx @@ -9,7 +9,7 @@ import Account from './components/account'; import EditListForm from './components/edit-list-form'; import Search from './components/search'; -import type { ModalType } from '../ui/components/modal-root'; +import type { BaseModalProps } from '../../modal-root'; const messages = defineMessages({ changeTitle: { id: 'lists.edit.submit', defaultMessage: 'Change title' }, @@ -18,12 +18,11 @@ const messages = defineMessages({ editList: { id: 'lists.edit', defaultMessage: 'Edit list' }, }); -interface IListEditor { +interface ListEditorModalProps { listId: string; - onClose: (type: ModalType) => void; } -const ListEditor: React.FC = ({ listId, onClose }) => { +const ListEditorModal: React.FC = ({ listId, onClose }) => { const intl = useIntl(); const dispatch = useAppDispatch(); @@ -76,4 +75,4 @@ const ListEditor: React.FC = ({ listId, onClose }) => { ); }; -export { ListEditor as default }; +export { ListEditorModal as default, type ListEditorModalProps }; diff --git a/src/features/ui/components/modals/manage-group-modal/create-group-modal.tsx b/src/features/ui/components/modals/manage-group-modal/create-group-modal.tsx index 7f507d35c..d89f5bd68 100644 --- a/src/features/ui/components/modals/manage-group-modal/create-group-modal.tsx +++ b/src/features/ui/components/modals/manage-group-modal/create-group-modal.tsx @@ -9,7 +9,7 @@ import toast from 'soapbox/toast'; import ConfirmationStep from './steps/confirmation-step'; import DetailsStep from './steps/details-step'; -import type { ModalType } from '../../modal-root'; +import type { BaseModalProps } from '../../modal-root'; import type { CreateGroupParams } from 'pl-api'; import type { PlfeResponse } from 'soapbox/api'; import type { Group } from 'soapbox/normalizers'; @@ -23,12 +23,7 @@ enum Steps { ONE = 'ONE', TWO = 'TWO', } - -interface ICreateGroupModal { - onClose: (type?: ModalType) => void; -} - -const CreateGroupModal: React.FC = ({ onClose }) => { +const CreateGroupModal: React.FC = ({ onClose }) => { const intl = useIntl(); const [group, setGroup] = useState(null); diff --git a/src/features/ui/components/modals/media-modal.tsx b/src/features/ui/components/modals/media-modal.tsx index f278fe439..721f5faad 100644 --- a/src/features/ui/components/modals/media-modal.tsx +++ b/src/features/ui/components/modals/media-modal.tsx @@ -19,6 +19,7 @@ import { makeGetStatus } from 'soapbox/selectors'; import ImageLoader from '../image-loader'; +import type { BaseModalProps } from '../modal-root'; import type { MediaAttachment } from 'pl-api'; const messages = defineMessages({ @@ -42,15 +43,14 @@ const containerStyle: React.CSSProperties = { alignItems: 'center', // center vertically }; -interface IMediaModal { +interface MediaModalProps { media: Array; statusId?: string; index: number; time?: number; - onClose(): void; } -const MediaModal: React.FC = (props) => { +const MediaModal: React.FC = (props) => { const { media, statusId, @@ -245,7 +245,7 @@ const MediaModal: React.FC = (props) => { onClose()} theme='dark' className='!p-1.5 hover:scale-105 hover:bg-gray-900' iconClassName='h-5 w-5' @@ -348,4 +348,4 @@ const MediaModal: React.FC = (props) => { ); }; -export { MediaModal as default }; +export { type MediaModalProps, MediaModal as default }; diff --git a/src/features/ui/components/modals/mentions-modal.tsx b/src/features/ui/components/modals/mentions-modal.tsx index 88906b6a8..29209b666 100644 --- a/src/features/ui/components/modals/mentions-modal.tsx +++ b/src/features/ui/components/modals/mentions-modal.tsx @@ -9,14 +9,13 @@ import AccountContainer from 'soapbox/containers/account-container'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; import { makeGetStatus } from 'soapbox/selectors'; -import type { ModalType } from '../modal-root'; +import type { BaseModalProps } from '../modal-root'; -interface IMentionsModal { - onClose: (type: ModalType) => void; +interface MentionsModalProps { statusId: string; } -const MentionsModal: React.FC = ({ onClose, statusId }) => { +const MentionsModal: React.FC = ({ onClose, statusId }) => { const dispatch = useAppDispatch(); const intl = useIntl(); const getStatus = useCallback(makeGetStatus(), []); @@ -64,4 +63,4 @@ const MentionsModal: React.FC = ({ onClose, statusId }) => { ); }; -export { MentionsModal as default }; +export { MentionsModal as default, type MentionsModalProps }; diff --git a/src/features/ui/components/modals/missing-description-modal.tsx b/src/features/ui/components/modals/missing-description-modal.tsx index 6d3d3180a..7eb1fc09c 100644 --- a/src/features/ui/components/modals/missing-description-modal.tsx +++ b/src/features/ui/components/modals/missing-description-modal.tsx @@ -3,18 +3,19 @@ import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; import { Modal } from 'soapbox/components/ui'; +import type { BaseModalProps } from '../modal-root'; + const messages = defineMessages({ modalTitle: { id: 'missing_description_modal.text', defaultMessage: 'You have not entered a description for all attachments. Continue anyway?' }, post: { id: 'missing_description_modal.continue', defaultMessage: 'Post' }, cancel: { id: 'missing_description_modal.cancel', defaultMessage: 'Cancel' }, }); -interface IMissingDescriptionModal { - onClose: () => void; +interface MissingDescriptionModalProps { onContinue: () => void; } -const MissingDescriptionModal: React.FC = ({ onClose, onContinue }) => { +const MissingDescriptionModal: React.FC = ({ onClose, onContinue }) => { const intl = useIntl(); return ( @@ -33,4 +34,4 @@ const MissingDescriptionModal: React.FC = ({ onClose, ); }; -export { MissingDescriptionModal as default }; +export { MissingDescriptionModal as default, type MissingDescriptionModalProps }; diff --git a/src/features/ui/components/modals/reactions-modal.tsx b/src/features/ui/components/modals/reactions-modal.tsx index d3a2a4b7e..92cd54517 100644 --- a/src/features/ui/components/modals/reactions-modal.tsx +++ b/src/features/ui/components/modals/reactions-modal.tsx @@ -10,7 +10,7 @@ import AccountContainer from 'soapbox/containers/account-container'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; import { ReactionRecord } from 'soapbox/reducers/user-lists'; -import type { ModalType } from '../modal-root'; +import type { BaseModalProps } from '../modal-root'; import type { Item } from 'soapbox/components/ui/tabs/tabs'; const messages = defineMessages({ @@ -23,13 +23,12 @@ interface IAccountWithReaction { reactionUrl?: string; } -interface IReactionsModal { - onClose: (type: ModalType) => void; +interface ReactionsModalProps { statusId: string; reaction?: string; } -const ReactionsModal: React.FC = ({ onClose, statusId, reaction: initialReaction }) => { +const ReactionsModal: React.FC = ({ onClose, statusId, reaction: initialReaction }) => { const dispatch = useAppDispatch(); const intl = useIntl(); const [reaction, setReaction] = useState(initialReaction); @@ -122,4 +121,4 @@ const ReactionsModal: React.FC = ({ onClose, statusId, reaction ); }; -export { ReactionsModal as default }; +export { ReactionsModal as default, type ReactionsModalProps }; diff --git a/src/features/ui/components/modals/reblogs-modal.tsx b/src/features/ui/components/modals/reblogs-modal.tsx index 0cb107c2c..63198a719 100644 --- a/src/features/ui/components/modals/reblogs-modal.tsx +++ b/src/features/ui/components/modals/reblogs-modal.tsx @@ -8,14 +8,13 @@ import { Modal, Spinner } from 'soapbox/components/ui'; import AccountContainer from 'soapbox/containers/account-container'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; -import type { ModalType } from '../modal-root'; +import type { BaseModalProps } from '../modal-root'; -interface IReblogsModal { - onClose: (type: ModalType) => void; +interface ReblogsModalProps { statusId: string; } -const ReblogsModal: React.FC = ({ onClose, statusId }) => { +const ReblogsModal: React.FC = ({ onClose, statusId }) => { const dispatch = useAppDispatch(); const intl = useIntl(); const accountIds = useAppSelector((state) => state.user_lists.reblogged_by.get(statusId)?.items); @@ -75,4 +74,4 @@ const ReblogsModal: React.FC = ({ onClose, statusId }) => { ); }; -export { ReblogsModal as default }; +export { ReblogsModal as default, type ReblogsModalProps }; diff --git a/src/features/ui/components/modals/reply-mentions-modal.tsx b/src/features/ui/components/modals/reply-mentions-modal.tsx index aee0f6bd8..db50be0e1 100644 --- a/src/features/ui/components/modals/reply-mentions-modal.tsx +++ b/src/features/ui/components/modals/reply-mentions-modal.tsx @@ -7,14 +7,13 @@ import { useAppSelector, useCompose, useOwnAccount } from 'soapbox/hooks'; import { statusToMentionsAccountIdsArray } from 'soapbox/reducers/compose'; import { makeGetStatus } from 'soapbox/selectors'; -import type { ModalType } from '../modal-root'; +import type { BaseModalProps } from '../modal-root'; -interface IReplyMentionsModal { +interface ReplyMentionsModalProps { composeId: string; - onClose: (type: ModalType) => void; } -const ReplyMentionsModal: React.FC = ({ composeId, onClose }) => { +const ReplyMentionsModal: React.FC = ({ composeId, onClose }) => { const compose = useCompose(composeId); const getStatus = useCallback(makeGetStatus(), []); @@ -42,4 +41,4 @@ const ReplyMentionsModal: React.FC = ({ composeId, onClose ); }; -export { ReplyMentionsModal as default }; +export { ReplyMentionsModal as default, type ReplyMentionsModalProps }; diff --git a/src/features/ui/components/modals/report-modal/report-modal.tsx b/src/features/ui/components/modals/report-modal/report-modal.tsx index 8b75e7c14..5f739a275 100644 --- a/src/features/ui/components/modals/report-modal/report-modal.tsx +++ b/src/features/ui/components/modals/report-modal/report-modal.tsx @@ -15,6 +15,8 @@ import ConfirmationStep from './steps/confirmation-step'; import OtherActionsStep from './steps/other-actions-step'; import ReasonStep from './steps/reason-step'; +import type { BaseModalProps } from '../../modal-root'; + const messages = defineMessages({ blankslate: { id: 'report.reason.blankslate', defaultMessage: 'You have removed all statuses from being selected.' }, done: { id: 'report.done', defaultMessage: 'Done' }, @@ -68,11 +70,7 @@ const SelectedStatus = ({ statusId }: { statusId: string }) => { ); }; -interface IReportModal { - onClose: () => void; -} - -const ReportModal = ({ onClose }: IReportModal) => { +const ReportModal = ({ onClose }: BaseModalProps) => { const dispatch = useAppDispatch(); const intl = useIntl(); diff --git a/src/features/ui/components/modals/report-modal/steps/confirmation-step.tsx b/src/features/ui/components/modals/report-modal/steps/confirmation-step.tsx index de5b46978..90ffc946f 100644 --- a/src/features/ui/components/modals/report-modal/steps/confirmation-step.tsx +++ b/src/features/ui/components/modals/report-modal/steps/confirmation-step.tsx @@ -5,18 +5,12 @@ import { getSoapboxConfig } from 'soapbox/actions/soapbox'; import { Stack, Text } from 'soapbox/components/ui'; import { useAppSelector } from 'soapbox/hooks'; -import type { Account } from 'soapbox/normalizers'; - const messages = defineMessages({ accountEntity: { id: 'report.confirmation.entity.account', defaultMessage: 'account' }, title: { id: 'report.confirmation.title', defaultMessage: 'Thanks for submitting your report.' }, content: { id: 'report.confirmation.content', defaultMessage: 'If we find that this {entity} is violating the {link} we will take further action on the matter.' }, }); -interface IConfirmationStep { - account?: Account; -} - const termsOfServiceText = ( ( ); -const ConfirmationStep: React.FC = () => { +const ConfirmationStep: React.FC = () => { const intl = useIntl(); const links = useAppSelector((state) => getSoapboxConfig(state).get('links') as any); diff --git a/src/features/ui/components/modals/report-modal/steps/other-actions-step.tsx b/src/features/ui/components/modals/report-modal/steps/other-actions-step.tsx index 1764c5863..64ed70b27 100644 --- a/src/features/ui/components/modals/report-modal/steps/other-actions-step.tsx +++ b/src/features/ui/components/modals/report-modal/steps/other-actions-step.tsx @@ -19,7 +19,7 @@ const messages = defineMessages({ }); interface IOtherActionsStep { - account: Account; + account: Pick; } const OtherActionsStep = ({ account }: IOtherActionsStep) => { diff --git a/src/features/ui/components/modals/select-bookmark-folder-modal.tsx b/src/features/ui/components/modals/select-bookmark-folder-modal.tsx index bf0be7d42..dfcab9776 100644 --- a/src/features/ui/components/modals/select-bookmark-folder-modal.tsx +++ b/src/features/ui/components/modals/select-bookmark-folder-modal.tsx @@ -9,15 +9,14 @@ import NewFolderForm from 'soapbox/features/bookmark-folders/components/new-fold import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; import { makeGetStatus } from 'soapbox/selectors'; -import type { ModalType } from '../modal-root'; +import type { BaseModalProps } from '../modal-root'; import type { Status as StatusEntity } from 'soapbox/normalizers'; -interface ISelectBookmarkFolderModal { +interface SelectBookmarkFolderModalProps { statusId: string; - onClose: (type: ModalType) => void; } -const SelectBookmarkFolderModal: React.FC = ({ statusId, onClose }) => { +const SelectBookmarkFolderModal: React.FC = ({ statusId, onClose }) => { const getStatus = useCallback(makeGetStatus(), []); const status = useAppSelector(state => getStatus(state, { id: statusId })) as StatusEntity; const dispatch = useAppDispatch(); @@ -95,4 +94,4 @@ const SelectBookmarkFolderModal: React.FC = ({ statu ); }; -export { SelectBookmarkFolderModal as default }; +export { type SelectBookmarkFolderModalProps, SelectBookmarkFolderModal as default }; diff --git a/src/features/ui/components/modals/text-field-modal.tsx b/src/features/ui/components/modals/text-field-modal.tsx index 66c7a0ef5..fad01b1c5 100644 --- a/src/features/ui/components/modals/text-field-modal.tsx +++ b/src/features/ui/components/modals/text-field-modal.tsx @@ -3,21 +3,20 @@ import { FormattedMessage } from 'react-intl'; import { Modal, Stack, Textarea } from 'soapbox/components/ui'; -import type { ModalType } from '../modal-root'; +import type { BaseModalProps } from '../modal-root'; import type { ButtonThemes } from 'soapbox/components/ui/button/useButtonStyles'; -interface ITextFieldModal { +interface TextFieldModalProps { heading: React.ReactNode; placeholder?: string; confirm: React.ReactNode; - onClose: (type: ModalType) => void; onConfirm: (value?: string) => void; - onCancel: () => void; + onCancel?: () => void; confirmationTheme?: ButtonThemes; text?: string; } -const TextFieldModal: React.FC = ({ +const TextFieldModal: React.FC = ({ heading, placeholder, confirm, @@ -60,4 +59,4 @@ const TextFieldModal: React.FC = ({ ); }; -export { TextFieldModal as default }; +export { type TextFieldModalProps, TextFieldModal as default }; diff --git a/src/features/ui/components/modals/unauthorized-modal.tsx b/src/features/ui/components/modals/unauthorized-modal.tsx index 43b61d961..3e8a0f18a 100644 --- a/src/features/ui/components/modals/unauthorized-modal.tsx +++ b/src/features/ui/components/modals/unauthorized-modal.tsx @@ -8,18 +8,18 @@ import { useAppSelector, useAppDispatch, useFeatures, useInstance, useRegistrati import { selectAccount } from 'soapbox/selectors'; import toast from 'soapbox/toast'; -import type { ModalType } from '../modal-root'; +import type { BaseModalProps } from '../modal-root'; const messages = defineMessages({ accountPlaceholder: { id: 'remote_interaction.account_placeholder', defaultMessage: 'Enter your username@domain you want to act from' }, userNotFoundError: { id: 'remote_interaction.user_not_found_error', defaultMessage: 'Couldn\'t find given user' }, }); -interface IUnauthorizedModal { +type UnauthorizedModalAction = 'FOLLOW' | 'REPLY' | 'REBLOG' | 'FAVOURITE' | 'DISLIKE' | 'POLL_VOTE' | 'JOIN'; + +interface UnauthorizedModalProps { /** Unauthorized action type. */ - action: 'FOLLOW' | 'REPLY' | 'REBLOG' | 'FAVOURITE' | 'DISLIKE' | 'POLL_VOTE' | 'JOIN'; - /** Close event handler. */ - onClose: (modalType: ModalType) => void; + action?: UnauthorizedModalAction; /** ActivityPub ID of the account OR status being acted upon. */ ap_id?: string; /** Account ID of the account being acted upon. */ @@ -27,7 +27,7 @@ interface IUnauthorizedModal { } /** Modal to display when a logged-out user tries to do something that requires login. */ -const UnauthorizedModal: React.FC = ({ action, onClose, account: accountId, ap_id: apId }) => { +const UnauthorizedModal: React.FC = ({ action, onClose, account: accountId, ap_id: apId }) => { const intl = useIntl(); const history = useHistory(); const dispatch = useAppDispatch(); @@ -158,4 +158,4 @@ const UnauthorizedModal: React.FC = ({ action, onClose, acco ); }; -export { UnauthorizedModal as default }; +export { type UnauthorizedModalAction, type UnauthorizedModalProps, UnauthorizedModal as default }; diff --git a/src/features/ui/components/modals/video-modal.tsx b/src/features/ui/components/modals/video-modal.tsx index 1efc8c62f..ded3236a2 100644 --- a/src/features/ui/components/modals/video-modal.tsx +++ b/src/features/ui/components/modals/video-modal.tsx @@ -1,24 +1,32 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import { FormattedMessage } from 'react-intl'; import { useHistory } from 'react-router-dom'; import Video from 'soapbox/features/video'; +import { useAppSelector } from 'soapbox/hooks'; +import { makeGetStatus } from 'soapbox/selectors'; +import type { BaseModalProps } from '../modal-root'; import type { MediaAttachment } from 'pl-api'; -import type { Account, Status } from 'soapbox/normalizers'; +import type { Account } from 'soapbox/normalizers'; -interface IVideoModal { + +type VideoModalProps = { media: MediaAttachment; - status: Pick; - account: Account; - time: number; - onClose: () => void; -} + statusId: string; + account?: Pick; + time?: number; +}; -const VideoModal: React.FC = ({ status, account, media, time, onClose }) => { +const VideoModal: React.FC = ({ statusId, account, media, time }) => { + const getStatus = useCallback(makeGetStatus(), []); + + const status = useAppSelector(state => getStatus(state, { id: statusId }))!; const history = useHistory(); const handleStatusClick: React.MouseEventHandler = e => { + if (!account) return; + if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); history.push(`/@${account.acct}/posts/${status.id}`); @@ -48,4 +56,4 @@ const VideoModal: React.FC = ({ status, account, media, time, onClo ); }; -export { VideoModal as default }; +export { type VideoModalProps, VideoModal as default }; diff --git a/src/features/ui/components/profile-media-panel.tsx b/src/features/ui/components/profile-media-panel.tsx index 54decb0c6..9c65ae1da 100644 --- a/src/features/ui/components/profile-media-panel.tsx +++ b/src/features/ui/components/profile-media-panel.tsx @@ -25,7 +25,7 @@ const ProfileMediaPanel: React.FC = ({ account }) => { const handleOpenMedia = (attachment: AccountGalleryAttachment): void => { if (attachment.type === 'video') { - dispatch(openModal('VIDEO', { media: attachment, status: attachment.status })); + dispatch(openModal('VIDEO', { media: attachment, statusId: attachment.status.id })); } else { const media = attachment.status.media_attachments; const index = media.findIndex(x => x.id === attachment.id); diff --git a/src/features/ui/containers/modal-container.ts b/src/features/ui/containers/modal-container.ts deleted file mode 100644 index 0c0445df2..000000000 --- a/src/features/ui/containers/modal-container.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { connect } from 'react-redux'; - -import { cancelReplyCompose } from 'soapbox/actions/compose'; -import { cancelEventCompose } from 'soapbox/actions/events'; -import { closeModal } from 'soapbox/actions/modals'; -import { cancelReport } from 'soapbox/actions/reports'; - -import ModalRoot, { ModalType } from '../components/modal-root'; - -import type { AppDispatch, RootState } from 'soapbox/store'; - -const mapStateToProps = (state: RootState) => { - const modal = state.modals.last({ - modalType: null, - modalProps: {}, - }); - - return { - type: modal.modalType as ModalType, - props: modal.modalProps, - }; -}; - -const mapDispatchToProps = (dispatch: AppDispatch) => ({ - onClose(type?: ModalType) { - switch (type) { - case 'COMPOSE': - dispatch(cancelReplyCompose()); - break; - case 'COMPOSE_EVENT': - dispatch(cancelEventCompose()); - break; - case 'REPORT': - dispatch(cancelReport()); - break; - default: - break; - } - - dispatch(closeModal(type)); - }, -}); - -const ModalContainer = connect(mapStateToProps, mapDispatchToProps)(ModalRoot); - -export { ModalContainer as default }; diff --git a/src/features/ui/util/async-components.ts b/src/features/ui/util/async-components.ts index 39849d1ec..1c84fa038 100644 --- a/src/features/ui/util/async-components.ts +++ b/src/features/ui/util/async-components.ts @@ -25,37 +25,12 @@ export const FavouritedStatuses = lazy(() => import('soapbox/features/favourited export const Blocks = lazy(() => import('soapbox/features/blocks')); export const DomainBlocks = lazy(() => import('soapbox/features/domain-blocks')); export const Mutes = lazy(() => import('soapbox/features/mutes')); -export const MuteModal = lazy(() => import('soapbox/features/ui/components/modals/mute-modal')); export const Filters = lazy(() => import('soapbox/features/filters')); export const EditFilter = lazy(() => import('soapbox/features/filters/edit-filter')); -export const ReportModal = lazy(() => import('soapbox/features/ui/components/modals/report-modal/report-modal')); -export const AccountModerationModal = lazy(() => import('soapbox/features/ui/components/modals/account-moderation-modal/account-moderation-modal')); export const MediaGallery = lazy(() => import('soapbox/components/media-gallery')); export const Video = lazy(() => import('soapbox/features/video')); export const Audio = lazy(() => import('soapbox/features/audio')); -export const MediaModal = lazy(() => import('soapbox/features/ui/components/modals/media-modal')); -export const VideoModal = lazy(() => import('soapbox/features/ui/components/modals/video-modal')); -export const BoostModal = lazy(() => import('soapbox/features/ui/components/modals/boost-modal')); -export const ConfirmationModal = lazy(() => import('soapbox/features/ui/components/modals/confirmation-modal')); -export const MissingDescriptionModal = lazy(() => import('soapbox/features/ui/components/modals/missing-description-modal')); -export const ActionsModal = lazy(() => import('soapbox/features/ui/components/modals/actions-modal')); -export const HotkeysModal = lazy(() => import('soapbox/features/ui/components/modals/hotkeys-modal')); -export const ComposeModal = lazy(() => import('soapbox/features/ui/components/modals/compose-modal')); -export const ReplyMentionsModal = lazy(() => import('soapbox/features/ui/components/modals/reply-mentions-modal')); -export const UnauthorizedModal = lazy(() => import('soapbox/features/ui/components/modals/unauthorized-modal')); -export const EditFederationModal = lazy(() => import('soapbox/features/ui/components/modals/edit-federation-modal')); -export const EmbedModal = lazy(() => import('soapbox/features/ui/components/modals/embed-modal')); -export const ComponentModal = lazy(() => import('soapbox/features/ui/components/modals/component-modal')); -export const ReblogsModal = lazy(() => import('soapbox/features/ui/components/modals/reblogs-modal')); -export const FavouritesModal = lazy(() => import('soapbox/features/ui/components/modals/favourites-modal')); -export const DislikesModal = lazy(() => import('soapbox/features/ui/components/modals/dislikes-modal')); -export const ReactionsModal = lazy(() => import('soapbox/features/ui/components/modals/reactions-modal')); -export const MentionsModal = lazy(() => import('soapbox/features/ui/components/modals/mentions-modal')); -export const LandingPageModal = lazy(() => import('soapbox/features/ui/components/modals/landing-page-modal')); -export const BirthdaysModal = lazy(() => import('soapbox/features/ui/components/modals/birthdays-modal')); export const BirthdayPanel = lazy(() => import('soapbox/components/birthday-panel')); -export const ListEditor = lazy(() => import('soapbox/features/list-editor')); -export const ListAdder = lazy(() => import('soapbox/features/list-adder')); export const Search = lazy(() => import('soapbox/features/search')); export const LoginPage = lazy(() => import('soapbox/features/auth-login/components/login-page')); export const ExternalLogin = lazy(() => import('soapbox/features/external-login')); @@ -91,13 +66,12 @@ export const InstanceInfoPanel = lazy(() => import('soapbox/features/ui/componen export const InstanceModerationPanel = lazy(() => import('soapbox/features/ui/components/instance-moderation-panel')); export const LatestAccountsPanel = lazy(() => import('soapbox/features/admin/components/latest-accounts-panel')); export const SidebarMenu = lazy(() => import('soapbox/components/sidebar-menu')); -export const ModalContainer = lazy(() => import('soapbox/features/ui/containers/modal-container')); +export const ModalRoot = lazy(() => import('soapbox/features/ui/components/modal-root')); export const ProfileHoverCard = lazy(() => import('soapbox/components/profile-hover-card')); export const StatusHoverCard = lazy(() => import('soapbox/components/status-hover-card')); export const CryptoDonate = lazy(() => import('soapbox/features/crypto-donate')); export const CryptoDonatePanel = lazy(() => import('soapbox/features/crypto-donate/components/crypto-donate-panel')); export const CryptoAddress = lazy(() => import('soapbox/features/crypto-donate/components/crypto-address')); -export const CryptoDonateModal = lazy(() => import('soapbox/features/ui/components/modals/crypto-donate-modal')); export const LightningAddress = lazy(() => import('soapbox/features/crypto-donate/components/lightning-address')); export const ScheduledStatuses = lazy(() => import('soapbox/features/scheduled-statuses')); export const UserIndex = lazy(() => import('soapbox/features/admin/user-index')); @@ -118,18 +92,12 @@ export const TestTimeline = lazy(() => import('soapbox/features/test-timeline')) export const ServiceWorkerInfo = lazy(() => import('soapbox/features/developers/service-worker-info')); export const DatePicker = lazy(() => import('soapbox/features/birthdays/date-picker')); export const OnboardingWizard = lazy(() => import('soapbox/features/onboarding/onboarding-wizard')); -export const CompareHistoryModal = lazy(() => import('soapbox/features/ui/components/modals/compare-history-modal')); export const AuthTokenList = lazy(() => import('soapbox/features/auth-token-list')); -export const FamiliarFollowersModal = lazy(() => import('soapbox/features/ui/components/modals/familiar-followers-modal')); export const AnnouncementsPanel = lazy(() => import('soapbox/components/announcements/announcements-panel')); export const Quotes = lazy(() => import('soapbox/features/quotes')); -export const ComposeEventModal = lazy(() => import('soapbox/features/ui/components/modals/compose-event-modal/compose-event-modal')); -export const JoinEventModal = lazy(() => import('soapbox/features/ui/components/modals/join-event-modal')); export const EventHeader = lazy(() => import('soapbox/features/event/components/event-header')); export const EventInformation = lazy(() => import('soapbox/features/event/event-information')); export const EventDiscussion = lazy(() => import('soapbox/features/event/event-discussion')); -export const EventMapModal = lazy(() => import('soapbox/features/ui/components/modals/event-map-modal')); -export const EventParticipantsModal = lazy(() => import('soapbox/features/ui/components/modals/event-participants-modal')); export const Events = lazy(() => import('soapbox/features/events')); export const Groups = lazy(() => import('soapbox/features/groups')); export const GroupMembers = lazy(() => import('soapbox/features/group/group-members')); @@ -139,25 +107,19 @@ export const EditGroup = lazy(() => import('soapbox/features/group/edit-group')) export const GroupBlockedMembers = lazy(() => import('soapbox/features/group/group-blocked-members')); export const GroupMembershipRequests = lazy(() => import('soapbox/features/group/group-membership-requests')); export const GroupGallery = lazy(() => import('soapbox/features/group/group-gallery')); -export const CreateGroupModal = lazy(() => import('soapbox/features/ui/components/modals/manage-group-modal/create-group-modal')); export const NewGroupPanel = lazy(() => import('soapbox/features/ui/components/panels/new-group-panel')); export const MyGroupsPanel = lazy(() => import('soapbox/features/ui/components/panels/my-groups-panel')); export const GroupMediaPanel = lazy(() => import('soapbox/features/ui/components/group-media-panel')); export const NewEventPanel = lazy(() => import('soapbox/features/ui/components/panels/new-event-panel')); export const Announcements = lazy(() => import('soapbox/features/admin/announcements')); -export const EditAnnouncementModal = lazy(() => import('soapbox/features/ui/components/modals/edit-announcement-modal')); export const FollowedTags = lazy(() => import('soapbox/features/followed-tags')); export const AccountNotePanel = lazy(() => import('soapbox/features/ui/components/panels/account-note-panel')); export const ComposeEditor = lazy(() => import('soapbox/features/compose/editor')); export const BookmarkFolders = lazy(() => import('soapbox/features/bookmark-folders')); -export const EditBookmarkFolderModal = lazy(() => import('soapbox/features/ui/components/modals/edit-bookmark-folder-modal')); -export const SelectBookmarkFolderModal = lazy(() => import('soapbox/features/ui/components/modals/select-bookmark-folder-modal')); export const Domains = lazy(() => import('soapbox/features/admin/domains')); -export const EditDomainModal = lazy(() => import('soapbox/features/ui/components/modals/edit-domain-modal')); export const Relays = lazy(() => import('soapbox/features/admin/relays')); export const Rules = lazy(() => import('soapbox/features/admin/rules')); -export const EditRuleModal = lazy(() => import('soapbox/features/ui/components/modals/edit-rule-modal')); export const DraftStatuses = lazy(() => import('soapbox/features/draft-statuses')); export const Circle = lazy(() => import('soapbox/features/circle')); export const BubbleTimeline = lazy(() => import('soapbox/features/bubble-timeline')); -export const TextFieldModal = lazy(() => import('soapbox/features/ui/components/modals/text-field-modal')); + diff --git a/src/init/soapbox-mount.tsx b/src/init/soapbox-mount.tsx index 61dc3a4b8..fafa2e036 100644 --- a/src/init/soapbox-mount.tsx +++ b/src/init/soapbox-mount.tsx @@ -8,10 +8,7 @@ import { ScrollContext } from 'react-router-scroll-4'; import * as BuildConfig from 'soapbox/build-config'; import LoadingScreen from 'soapbox/components/loading-screen'; import SiteErrorBoundary from 'soapbox/components/site-error-boundary'; -import { - ModalContainer, - OnboardingWizard, -} from 'soapbox/features/ui/util/async-components'; +import { ModalRoot, OnboardingWizard } from 'soapbox/features/ui/util/async-components'; import { useAppSelector, useLoggedIn, @@ -71,7 +68,7 @@ const SoapboxMount = () => { - + {(gdpr && !isLoggedIn) && ( diff --git a/src/reducers/modals.ts b/src/reducers/modals.ts index b30675cdc..6450e0492 100644 --- a/src/reducers/modals.ts +++ b/src/reducers/modals.ts @@ -1,18 +1,18 @@ import { List as ImmutableList, Record as ImmutableRecord } from 'immutable'; -import { MODAL_OPEN, MODAL_CLOSE } from '../actions/modals'; +import { MODAL_OPEN, MODAL_CLOSE, type ModalsAction } from '../actions/modals'; -import type { AnyAction } from 'redux'; +import type { ModalType } from 'soapbox/features/ui/components/modal-root'; const ModalRecord = ImmutableRecord({ - modalType: '', + modalType: null as ModalType | null, modalProps: null as Record | null, }); type Modal = ReturnType; type State = ImmutableList; -const modal = (state: State = ImmutableList(), action: AnyAction) => { +const modal = (state: State = ImmutableList(), action: ModalsAction) => { switch (action.type) { case MODAL_OPEN: return state.push(ModalRecord({ modalType: action.modalType, modalProps: action.modalProps }));