From 14bc73e94cb40b10404468bb6bda75af2c9995a6 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 25 Jul 2024 19:05:54 +0200 Subject: [PATCH] [Glitch] Change design of confirmation modals in web UI Port 8818748b9023acd84f42bf887e361d9244521df4 to glitch-soc Co-authored-by: Renaud Chaput Signed-off-by: Claire --- .../flavours/glitch/components/domain.tsx | 14 +-- .../glitch/components/follow_button.tsx | 24 +---- .../glitch/containers/account_container.jsx | 21 ++--- .../glitch/containers/domain_container.jsx | 36 -------- .../glitch/containers/status_container.js | 50 ++--------- .../containers/header_container.jsx | 19 +--- .../compose/components/action_bar.jsx | 15 +--- .../glitch/features/compose/index.jsx | 15 +--- .../components/conversation.jsx | 13 +-- .../directory/components/account_card.tsx | 52 ++--------- .../glitch/features/domain_blocks/index.jsx | 5 +- .../glitch/features/list_timeline/index.jsx | 23 +---- .../containers/column_settings_container.js | 16 +--- .../picture_in_picture/components/footer.jsx | 14 +-- .../containers/detailed_status_container.js | 31 +------ .../flavours/glitch/features/status/index.jsx | 38 +++----- .../ui/components/confirmation_modal.jsx | 83 ------------------ .../clear_notifications.tsx | 46 ++++++++++ .../confirmation_modal.tsx | 87 +++++++++++++++++++ .../confirmation_modals/delete_list.tsx | 58 +++++++++++++ .../confirmation_modals/delete_status.tsx | 67 ++++++++++++++ .../confirmation_modals/edit_status.tsx | 45 ++++++++++ .../components/confirmation_modals/index.ts | 8 ++ .../confirmation_modals/log_out.tsx | 40 +++++++++ .../components/confirmation_modals/reply.tsx | 46 ++++++++++ .../confirmation_modals/unfollow.tsx | 50 +++++++++++ .../ui/components/disabled_account_banner.jsx | 20 +---- .../features/ui/components/link_footer.jsx | 21 +---- .../features/ui/components/modal_root.jsx | 18 +++- .../flavours/glitch/locales/en.json | 4 - .../flavours/glitch/styles/components.scss | 87 +++++-------------- 31 files changed, 554 insertions(+), 512 deletions(-) delete mode 100644 app/javascript/flavours/glitch/containers/domain_container.jsx delete mode 100644 app/javascript/flavours/glitch/features/ui/components/confirmation_modal.jsx create mode 100644 app/javascript/flavours/glitch/features/ui/components/confirmation_modals/clear_notifications.tsx create mode 100644 app/javascript/flavours/glitch/features/ui/components/confirmation_modals/confirmation_modal.tsx create mode 100644 app/javascript/flavours/glitch/features/ui/components/confirmation_modals/delete_list.tsx create mode 100644 app/javascript/flavours/glitch/features/ui/components/confirmation_modals/delete_status.tsx create mode 100644 app/javascript/flavours/glitch/features/ui/components/confirmation_modals/edit_status.tsx create mode 100644 app/javascript/flavours/glitch/features/ui/components/confirmation_modals/index.ts create mode 100644 app/javascript/flavours/glitch/features/ui/components/confirmation_modals/log_out.tsx create mode 100644 app/javascript/flavours/glitch/features/ui/components/confirmation_modals/reply.tsx create mode 100644 app/javascript/flavours/glitch/features/ui/components/confirmation_modals/unfollow.tsx diff --git a/app/javascript/flavours/glitch/components/domain.tsx b/app/javascript/flavours/glitch/components/domain.tsx index ed5e8e7e4c..e3eec2bd9b 100644 --- a/app/javascript/flavours/glitch/components/domain.tsx +++ b/app/javascript/flavours/glitch/components/domain.tsx @@ -3,6 +3,8 @@ import { useCallback } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import LockOpenIcon from '@/material-icons/400-24px/lock_open.svg?react'; +import { unblockDomain } from 'flavours/glitch/actions/domain_blocks'; +import { useAppDispatch } from 'flavours/glitch/store'; import { IconButton } from './icon_button'; @@ -13,17 +15,15 @@ const messages = defineMessages({ }, }); -interface Props { +export const Domain: React.FC<{ domain: string; - onUnblockDomain: (domain: string) => void; -} - -export const Domain: React.FC = ({ domain, onUnblockDomain }) => { +}> = ({ domain }) => { const intl = useIntl(); + const dispatch = useAppDispatch(); const handleDomainUnblock = useCallback(() => { - onUnblockDomain(domain); - }, [domain, onUnblockDomain]); + dispatch(unblockDomain(domain)); + }, [dispatch, domain]); return (
diff --git a/app/javascript/flavours/glitch/components/follow_button.tsx b/app/javascript/flavours/glitch/components/follow_button.tsx index a6b2064703..a27872c1aa 100644 --- a/app/javascript/flavours/glitch/components/follow_button.tsx +++ b/app/javascript/flavours/glitch/components/follow_button.tsx @@ -1,12 +1,11 @@ import { useCallback, useEffect } from 'react'; -import { useIntl, defineMessages, FormattedMessage } from 'react-intl'; +import { useIntl, defineMessages } from 'react-intl'; import { useIdentity } from '@/flavours/glitch/identity_context'; import { fetchRelationships, followAccount, - unfollowAccount, } from 'flavours/glitch/actions/accounts'; import { openModal } from 'flavours/glitch/actions/modal'; import { Button } from 'flavours/glitch/components/button'; @@ -59,29 +58,14 @@ export const FollowButton: React.FC<{ if (accountId === me) { return; - } else if (relationship.following || relationship.requested) { + } else if (account && (relationship.following || relationship.requested)) { dispatch( - openModal({ - modalType: 'CONFIRM', - modalProps: { - message: ( - @{account?.acct} }} - /> - ), - confirm: intl.formatMessage(messages.unfollow), - onConfirm: () => { - dispatch(unfollowAccount(accountId)); - }, - }, - }), + openModal({ modalType: 'CONFIRM_UNFOLLOW', modalProps: { account } }), ); } else { dispatch(followAccount(accountId)); } - }, [dispatch, intl, accountId, relationship, account, signedIn]); + }, [dispatch, accountId, relationship, account, signedIn]); let label; diff --git a/app/javascript/flavours/glitch/containers/account_container.jsx b/app/javascript/flavours/glitch/containers/account_container.jsx index f171fcc2fe..7fd186b657 100644 --- a/app/javascript/flavours/glitch/containers/account_container.jsx +++ b/app/javascript/flavours/glitch/containers/account_container.jsx @@ -1,24 +1,20 @@ -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { injectIntl } from 'react-intl'; import { connect } from 'react-redux'; +import { openModal } from 'flavours/glitch/actions/modal'; + import { followAccount, - unfollowAccount, blockAccount, unblockAccount, muteAccount, unmuteAccount, } from '../actions/accounts'; -import { openModal } from '../actions/modal'; import { initMuteModal } from '../actions/mutes'; import Account from '../components/account'; import { makeGetAccount } from '../selectors'; -const messages = defineMessages({ - unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' }, -}); - const makeMapStateToProps = () => { const getAccount = makeGetAccount(); @@ -29,18 +25,11 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -const mapDispatchToProps = (dispatch, { intl }) => ({ +const mapDispatchToProps = (dispatch) => ({ onFollow (account) { if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) { - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: @{account.get('acct')} }} />, - confirm: intl.formatMessage(messages.unfollowConfirm), - onConfirm: () => dispatch(unfollowAccount(account.get('id'))), - }, - })); + dispatch(openModal({ modalType: 'CONFIRM_UNFOLLOW', modalProps: { account } })); } else { dispatch(followAccount(account.get('id'))); } diff --git a/app/javascript/flavours/glitch/containers/domain_container.jsx b/app/javascript/flavours/glitch/containers/domain_container.jsx deleted file mode 100644 index c719a5775c..0000000000 --- a/app/javascript/flavours/glitch/containers/domain_container.jsx +++ /dev/null @@ -1,36 +0,0 @@ -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; - -import { connect } from 'react-redux'; - -import { blockDomain, unblockDomain } from '../actions/domain_blocks'; -import { openModal } from '../actions/modal'; -import { Domain } from '../components/domain'; - -const messages = defineMessages({ - blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Block entire domain' }, -}); - -const makeMapStateToProps = () => { - const mapStateToProps = () => ({}); - - return mapStateToProps; -}; - -const mapDispatchToProps = (dispatch, { intl }) => ({ - onBlockDomain (domain) { - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: {domain} }} />, - confirm: intl.formatMessage(messages.blockDomainConfirm), - onConfirm: () => dispatch(blockDomain(domain)), - }, - })); - }, - - onUnblockDomain (domain) { - dispatch(unblockDomain(domain)); - }, -}); - -export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Domain)); diff --git a/app/javascript/flavours/glitch/containers/status_container.js b/app/javascript/flavours/glitch/containers/status_container.js index 955adc5fa7..116a3455f7 100644 --- a/app/javascript/flavours/glitch/containers/status_container.js +++ b/app/javascript/flavours/glitch/containers/status_container.js @@ -1,5 +1,3 @@ -import { defineMessages, injectIntl } from 'react-intl'; - import { connect } from 'react-redux'; import { initBlockModal } from 'flavours/glitch/actions/blocks'; @@ -19,7 +17,6 @@ import { pin, unpin, } from 'flavours/glitch/actions/interactions'; -import { changeLocalSetting } from 'flavours/glitch/actions/local_settings'; import { openModal } from 'flavours/glitch/actions/modal'; import { initMuteModal } from 'flavours/glitch/actions/mutes'; import { deployPictureInPicture } from 'flavours/glitch/actions/picture_in_picture'; @@ -39,21 +36,6 @@ import { makeGetStatus, makeGetPictureInPicture } from 'flavours/glitch/selector import { showAlertForError } from '../actions/alerts'; -const messages = defineMessages({ - deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, - deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' }, - redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' }, - redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favorites and boosts will be lost, and replies to the original post will be orphaned.' }, - replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' }, - replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' }, - editConfirm: { id: 'confirmations.edit.confirm', defaultMessage: 'Edit' }, - editMessage: { id: 'confirmations.edit.message', defaultMessage: 'Editing now will overwrite the message you are currently composing. Are you sure you want to proceed?' }, - unfilterConfirm: { id: 'confirmations.unfilter.confirm', defaultMessage: 'Show' }, - author: { id: 'confirmations.unfilter.author', defaultMessage: 'Author' }, - matchingFilters: { id: 'confirmations.unfilter.filters', defaultMessage: 'Matching {count, plural, one {filter} other {filters}}' }, - editFilter: { id: 'confirmations.unfilter.edit_filter', defaultMessage: 'Edit filter' }, -}); - const makeMapStateToProps = () => { const getStatus = makeGetStatus(); const getPictureInPicture = makeGetPictureInPicture(); @@ -88,22 +70,14 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -const mapDispatchToProps = (dispatch, { intl, contextType }) => ({ +const mapDispatchToProps = (dispatch, { contextType }) => ({ onReply (status) { dispatch((_, getState) => { let state = getState(); if (state.getIn(['local_settings', 'confirm_before_clearing_draft']) && state.getIn(['compose', 'text']).trim().length !== 0) { - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: intl.formatMessage(messages.replyMessage), - confirm: intl.formatMessage(messages.replyConfirm), - onDoNotAsk: () => dispatch(changeLocalSetting(['confirm_before_clearing_draft'], false)), - onConfirm: () => dispatch(replyCompose(status)), - }, - })); + dispatch(openModal({ modalType: 'CONFIRM_REPLY', modalProps: { status } })); } else { dispatch(replyCompose(status)); } @@ -148,14 +122,7 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({ if (!deleteModal) { dispatch(deleteStatus(status.get('id'), withRedraft)); } else { - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage), - confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm), - onConfirm: () => dispatch(deleteStatus(status.get('id'), withRedraft)), - }, - })); + dispatch(openModal({ modalType: 'CONFIRM_DELETE_STATUS', modalProps: { statusId: status.get('id'), withRedraft } })); } }, @@ -163,14 +130,7 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({ dispatch((_, getState) => { let state = getState(); if (state.getIn(['compose', 'text']).trim().length !== 0) { - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: intl.formatMessage(messages.editMessage), - confirm: intl.formatMessage(messages.editConfirm), - onConfirm: () => dispatch(editStatus(status.get('id'))), - }, - })); + dispatch(openModal({ modalType: 'CONFIRM_EDIT_STATUS', modalProps: { statusId: status.get('id') } })); } else { dispatch(editStatus(status.get('id'))); } @@ -257,4 +217,4 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({ }); -export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Status)); +export default connect(makeMapStateToProps, mapDispatchToProps)(Status); diff --git a/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.jsx b/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.jsx index ba1241d829..3ceb201e2f 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.jsx +++ b/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.jsx @@ -1,10 +1,9 @@ -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { injectIntl } from 'react-intl'; import { connect } from 'react-redux'; import { followAccount, - unfollowAccount, unblockAccount, unmuteAccount, pinAccount, @@ -22,11 +21,6 @@ import { initReport } from '../../../actions/reports'; import { makeGetAccount, getAccountHidden } from '../../../selectors'; import Header from '../components/header'; -const messages = defineMessages({ - unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' }, - blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Block entire domain' }, -}); - const makeMapStateToProps = () => { const getAccount = makeGetAccount(); @@ -39,18 +33,11 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -const mapDispatchToProps = (dispatch, { intl }) => ({ +const mapDispatchToProps = (dispatch) => ({ onFollow (account) { if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) { - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: @{account.get('acct')} }} />, - confirm: intl.formatMessage(messages.unfollowConfirm), - onConfirm: () => dispatch(unfollowAccount(account.get('id'))), - }, - })); + dispatch(openModal({ modalType: 'CONFIRM_UNFOLLOW', modalProps: { account } })); } else { dispatch(followAccount(account.get('id'))); } diff --git a/app/javascript/flavours/glitch/features/compose/components/action_bar.jsx b/app/javascript/flavours/glitch/features/compose/components/action_bar.jsx index 7fda25ded5..43c2899373 100644 --- a/app/javascript/flavours/glitch/features/compose/components/action_bar.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/action_bar.jsx @@ -7,7 +7,6 @@ import { useDispatch } from 'react-redux'; import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; import { openModal } from 'flavours/glitch/actions/modal'; import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container'; -import { logOut } from 'flavours/glitch/utils/log_out'; const messages = defineMessages({ edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, @@ -23,8 +22,6 @@ const messages = defineMessages({ filters: { id: 'navigation_bar.filters', defaultMessage: 'Muted words' }, logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' }, - logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' }, - logoutConfirm: { id: 'confirmations.logout.confirm', defaultMessage: 'Log out' }, }); export const ActionBar = () => { @@ -32,16 +29,8 @@ export const ActionBar = () => { const intl = useIntl(); const handleLogoutClick = useCallback(() => { - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: intl.formatMessage(messages.logoutMessage), - confirm: intl.formatMessage(messages.logoutConfirm), - closeWhenConfirm: false, - onConfirm: () => logOut(), - }, - })); - }, [dispatch, intl]); + dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT' })); + }, [dispatch]); let menu = []; diff --git a/app/javascript/flavours/glitch/features/compose/index.jsx b/app/javascript/flavours/glitch/features/compose/index.jsx index b59fafe048..f9a763ef8b 100644 --- a/app/javascript/flavours/glitch/features/compose/index.jsx +++ b/app/javascript/flavours/glitch/features/compose/index.jsx @@ -24,7 +24,6 @@ import { Icon } from 'flavours/glitch/components/icon'; import glitchedElephant1 from 'flavours/glitch/images/mbstobon-ui-0.png'; import glitchedElephant2 from 'flavours/glitch/images/mbstobon-ui-1.png'; import glitchedElephant3 from 'flavours/glitch/images/mbstobon-ui-2.png'; -import { logOut } from 'flavours/glitch/utils/log_out'; import elephantUIPlane from '../../../../images/elephant_ui_plane.svg'; import { changeComposing, mountCompose, unmountCompose } from '../../actions/compose'; @@ -45,8 +44,6 @@ const messages = defineMessages({ settings: { id: 'navigation_bar.app_settings', defaultMessage: 'App settings' }, logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new post' }, - logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' }, - logoutConfirm: { id: 'confirmations.logout.confirm', defaultMessage: 'Log out' }, }); const mapStateToProps = (state, ownProps) => ({ @@ -88,20 +85,12 @@ class Compose extends PureComponent { } handleLogoutClick = e => { - const { dispatch, intl } = this.props; + const { dispatch } = this.props; e.preventDefault(); e.stopPropagation(); - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: intl.formatMessage(messages.logoutMessage), - confirm: intl.formatMessage(messages.logoutConfirm), - closeWhenConfirm: false, - onConfirm: () => logOut(), - }, - })); + dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT' })); return false; }; diff --git a/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx b/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx index 2c70f48a37..50b2a9d5ab 100644 --- a/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx +++ b/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx @@ -37,8 +37,6 @@ const messages = defineMessages({ delete: { id: 'conversation.delete', defaultMessage: 'Delete conversation' }, muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' }, unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' }, - replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' }, - replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' }, }); const getAccounts = createSelector( @@ -121,19 +119,12 @@ export const Conversation = ({ conversation, scrollKey, onMoveUp, onMoveDown }) let state = getState(); if (state.getIn(['compose', 'text']).trim().length !== 0) { - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: intl.formatMessage(messages.replyMessage), - confirm: intl.formatMessage(messages.replyConfirm), - onConfirm: () => dispatch(replyCompose(lastStatus)), - }, - })); + dispatch(openModal({ modalType: 'CONFIRM_REPLY', modalProps: { status: lastStatus } })); } else { dispatch(replyCompose(lastStatus)); } }); - }, [dispatch, lastStatus, intl]); + }, [dispatch, lastStatus]); const handleDelete = useCallback(() => { dispatch(deleteConversation(id)); diff --git a/app/javascript/flavours/glitch/features/directory/components/account_card.tsx b/app/javascript/flavours/glitch/features/directory/components/account_card.tsx index 907dbba9fd..8afb9f59d5 100644 --- a/app/javascript/flavours/glitch/features/directory/components/account_card.tsx +++ b/app/javascript/flavours/glitch/features/directory/components/account_card.tsx @@ -7,7 +7,6 @@ import classNames from 'classnames'; import { followAccount, - unfollowAccount, unblockAccount, unmuteAccount, } from 'flavours/glitch/actions/accounts'; @@ -29,20 +28,12 @@ const messages = defineMessages({ id: 'account.cancel_follow_request', defaultMessage: 'Withdraw follow request', }, - cancelFollowRequestConfirm: { - id: 'confirmations.cancel_follow_request.confirm', - defaultMessage: 'Withdraw request', - }, requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request', }, unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' }, unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' }, - unfollowConfirm: { - id: 'confirmations.unfollow.confirm', - defaultMessage: 'Unfollow', - }, edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, }); @@ -89,48 +80,17 @@ export const AccountCard: React.FC<{ accountId: string }> = ({ accountId }) => { const handleFollow = useCallback(() => { if (!account) return; - if (account.getIn(['relationship', 'following'])) { + if ( + account.getIn(['relationship', 'following']) || + account.getIn(['relationship', 'requested']) + ) { dispatch( - openModal({ - modalType: 'CONFIRM', - modalProps: { - message: ( - @{account.get('acct')} }} - /> - ), - confirm: intl.formatMessage(messages.unfollowConfirm), - onConfirm: () => { - dispatch(unfollowAccount(account.get('id'))); - }, - }, - }), - ); - } else if (account.getIn(['relationship', 'requested'])) { - dispatch( - openModal({ - modalType: 'CONFIRM', - modalProps: { - message: ( - @{account.get('acct')} }} - /> - ), - confirm: intl.formatMessage(messages.cancelFollowRequestConfirm), - onConfirm: () => { - dispatch(unfollowAccount(account.get('id'))); - }, - }, - }), + openModal({ modalType: 'CONFIRM_UNFOLLOW', modalProps: { account } }), ); } else { dispatch(followAccount(account.get('id'))); } - }, [account, dispatch, intl]); + }, [account, dispatch]); const handleBlock = useCallback(() => { if (account?.relationship?.blocking) { diff --git a/app/javascript/flavours/glitch/features/domain_blocks/index.jsx b/app/javascript/flavours/glitch/features/domain_blocks/index.jsx index 964eada9c1..67ee801cbf 100644 --- a/app/javascript/flavours/glitch/features/domain_blocks/index.jsx +++ b/app/javascript/flavours/glitch/features/domain_blocks/index.jsx @@ -11,16 +11,15 @@ import { connect } from 'react-redux'; import { debounce } from 'lodash'; import BlockIcon from '@/material-icons/400-24px/block-fill.svg?react'; +import { Domain } from 'flavours/glitch/components/domain'; import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks'; import { LoadingIndicator } from '../../components/loading_indicator'; import ScrollableList from '../../components/scrollable_list'; -import DomainContainer from '../../containers/domain_container'; import Column from '../ui/components/column'; const messages = defineMessages({ heading: { id: 'column.domain_blocks', defaultMessage: 'Blocked domains' }, - unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' }, }); const mapStateToProps = state => ({ @@ -70,7 +69,7 @@ class Blocks extends ImmutablePureComponent { bindToDocument={!multiColumn} > {domains.map(domain => - , + , )} diff --git a/app/javascript/flavours/glitch/features/list_timeline/index.jsx b/app/javascript/flavours/glitch/features/list_timeline/index.jsx index 08ce97f1ba..122e43669d 100644 --- a/app/javascript/flavours/glitch/features/list_timeline/index.jsx +++ b/app/javascript/flavours/glitch/features/list_timeline/index.jsx @@ -15,7 +15,7 @@ import DeleteIcon from '@/material-icons/400-24px/delete.svg?react'; import EditIcon from '@/material-icons/400-24px/edit.svg?react'; import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react'; import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; -import { fetchList, deleteList, updateList } from 'flavours/glitch/actions/lists'; +import { fetchList, updateList } from 'flavours/glitch/actions/lists'; import { openModal } from 'flavours/glitch/actions/modal'; import { connectListStream } from 'flavours/glitch/actions/streaming'; import { expandListTimeline } from 'flavours/glitch/actions/timelines'; @@ -29,8 +29,6 @@ import StatusListContainer from 'flavours/glitch/features/ui/containers/status_l import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; const messages = defineMessages({ - deleteMessage: { id: 'confirmations.delete_list.message', defaultMessage: 'Are you sure you want to permanently delete this list?' }, - deleteConfirm: { id: 'confirmations.delete_list.confirm', defaultMessage: 'Delete' }, followed: { id: 'lists.replies_policy.followed', defaultMessage: 'Any followed user' }, none: { id: 'lists.replies_policy.none', defaultMessage: 'No one' }, list: { id: 'lists.replies_policy.list', defaultMessage: 'Members of the list' }, @@ -125,25 +123,10 @@ class ListTimeline extends PureComponent { }; handleDeleteClick = () => { - const { dispatch, columnId, intl } = this.props; + const { dispatch, columnId } = this.props; const { id } = this.props.params; - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: intl.formatMessage(messages.deleteMessage), - confirm: intl.formatMessage(messages.deleteConfirm), - onConfirm: () => { - dispatch(deleteList(id)); - - if (columnId) { - dispatch(removeColumn(columnId)); - } else { - this.props.history.push('/lists'); - } - }, - }, - })); + dispatch(openModal({ modalType: 'CONFIRM_DELETE_LIST', modalProps: { listId: id, columnId } })); }; handleRepliesPolicyChange = ({ target }) => { diff --git a/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js b/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js index 55dcd4226c..1212a88b38 100644 --- a/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js +++ b/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js @@ -2,11 +2,10 @@ import { defineMessages, injectIntl } from 'react-intl'; import { connect } from 'react-redux'; +import { openModal } from 'flavours/glitch/actions/modal'; import { initializeNotifications } from 'flavours/glitch/actions/notifications_migration'; import { showAlert } from '../../../actions/alerts'; -import { openModal } from '../../../actions/modal'; -import { clearNotifications } from '../../../actions/notification_groups'; import { updateNotificationsPolicy } from '../../../actions/notification_policies'; import { setFilter, requestBrowserPermission } from '../../../actions/notifications'; import { changeAlerts as changePushNotifications } from '../../../actions/push_notifications'; @@ -14,8 +13,6 @@ import { changeSetting } from '../../../actions/settings'; import ColumnSettings from '../components/column_settings'; const messages = defineMessages({ - clearMessage: { id: 'notifications.clear_confirmation', defaultMessage: 'Are you sure you want to permanently clear all your notifications?' }, - clearConfirm: { id: 'notifications.clear', defaultMessage: 'Clear notifications' }, permissionDenied: { id: 'notifications.permission_denied_alert', defaultMessage: 'Desktop notifications can\'t be enabled, as browser permission has been denied before' }, }); @@ -31,7 +28,7 @@ const mapStateToProps = state => ({ notificationPolicy: state.notificationPolicy, }); -const mapDispatchToProps = (dispatch, { intl }) => ({ +const mapDispatchToProps = (dispatch) => ({ onChange (path, checked) { if (path[0] === 'push') { @@ -70,14 +67,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ }, onClear () { - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: intl.formatMessage(messages.clearMessage), - confirm: intl.formatMessage(messages.clearConfirm), - onConfirm: () => dispatch(clearNotifications()), - }, - })); + dispatch(openModal({ modalType: 'CONFIRM_CLEAR_NOTIFICATIONS' })); }, onRequestNotificationPermission () { diff --git a/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.jsx b/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.jsx index 3e26d84069..b57f882a74 100644 --- a/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.jsx +++ b/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.jsx @@ -31,8 +31,6 @@ const messages = defineMessages({ cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' }, cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' }, favourite: { id: 'status.favourite', defaultMessage: 'Favorite' }, - replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' }, - replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' }, open: { id: 'status.open', defaultMessage: 'Expand this status' }, }); @@ -73,19 +71,13 @@ class Footer extends ImmutablePureComponent { }; handleReplyClick = () => { - const { dispatch, askReplyConfirmation, status, intl } = this.props; + const { dispatch, askReplyConfirmation, status, onClose } = this.props; const { signedIn } = this.props.identity; if (signedIn) { if (askReplyConfirmation) { - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: intl.formatMessage(messages.replyMessage), - confirm: intl.formatMessage(messages.replyConfirm), - onConfirm: this._performReply, - }, - })); + onClose(true); + dispatch(openModal({ modalType: 'CONFIRM_REPLY', modalProps: { status } })); } else { this._performReply(); } diff --git a/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js b/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js index 1c9e3ccce1..34bde2fc6e 100644 --- a/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js +++ b/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js @@ -1,4 +1,4 @@ -import { defineMessages, injectIntl } from 'react-intl'; +import { injectIntl } from 'react-intl'; import { connect } from 'react-redux'; @@ -27,15 +27,6 @@ import { deleteModal } from '../../../initial_state'; import { makeGetStatus } from '../../../selectors'; import DetailedStatus from '../components/detailed_status'; -const messages = defineMessages({ - deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, - deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' }, - redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' }, - redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favorites and boosts will be lost, and replies to the original post will be orphaned.' }, - replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' }, - replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' }, -}); - const makeMapStateToProps = () => { const getStatus = makeGetStatus(); @@ -48,20 +39,13 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -const mapDispatchToProps = (dispatch, { intl }) => ({ +const mapDispatchToProps = (dispatch) => ({ onReply (status) { dispatch((_, getState) => { let state = getState(); if (state.getIn(['compose', 'text']).trim().length !== 0) { - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: intl.formatMessage(messages.replyMessage), - confirm: intl.formatMessage(messages.replyConfirm), - onConfirm: () => dispatch(replyCompose(status)), - }, - })); + dispatch(openModal({ modalType: 'CONFIRM_REPLY', modalProps: { status } })); } else { dispatch(replyCompose(status)); } @@ -98,14 +82,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ if (!deleteModal) { dispatch(deleteStatus(status.get('id'), withRedraft)); } else { - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage), - confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm), - onConfirm: () => dispatch(deleteStatus(status.get('id'), withRedraft)), - }, - })); + dispatch(openModal({ modalType: 'CONFIRM_DELETE_STATUS', modalProps: { statusId: status.get('id'), withRedraft } })); } }, diff --git a/app/javascript/flavours/glitch/features/status/index.jsx b/app/javascript/flavours/glitch/features/status/index.jsx index 2fca4b61b6..5bc99c6614 100644 --- a/app/javascript/flavours/glitch/features/status/index.jsx +++ b/app/javascript/flavours/glitch/features/status/index.jsx @@ -39,7 +39,6 @@ import { pin, unpin, } from '../../actions/interactions'; -import { changeLocalSetting } from '../../actions/local_settings'; import { openModal } from '../../actions/modal'; import { initMuteModal } from '../../actions/mutes'; import { initReport } from '../../actions/reports'; @@ -67,16 +66,10 @@ import DetailedStatus from './components/detailed_status'; const messages = defineMessages({ - deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, - deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' }, - redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' }, - redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favorites and boosts will be lost, and replies to the original post will be orphaned.' }, revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' }, hideAll: { id: 'status.show_less_all', defaultMessage: 'Show less for all' }, statusTitleWithAttachments: { id: 'status.title.with_attachments', defaultMessage: '{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}' }, detailedStatus: { id: 'status.detailed_status', defaultMessage: 'Detailed conversation view' }, - replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' }, - replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' }, tootHeading: { id: 'account.posts_with_replies', defaultMessage: 'Posts and replies' }, }); @@ -294,20 +287,12 @@ class Status extends ImmutablePureComponent { }; handleReplyClick = (status) => { - const { askReplyConfirmation, dispatch, intl } = this.props; + const { askReplyConfirmation, dispatch } = this.props; const { signedIn } = this.props.identity; if (signedIn) { if (askReplyConfirmation) { - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: intl.formatMessage(messages.replyMessage), - confirm: intl.formatMessage(messages.replyConfirm), - onDoNotAsk: () => dispatch(changeLocalSetting(['confirm_before_clearing_draft'], false)), - onConfirm: () => dispatch(replyCompose(status)), - }, - })); + dispatch(openModal({ modalType: 'CONFIRM_REPLY', modalProps: { status } })); } else { dispatch(replyCompose(status)); } @@ -350,24 +335,23 @@ class Status extends ImmutablePureComponent { }; handleDeleteClick = (status, withRedraft = false) => { - const { dispatch, intl } = this.props; + const { dispatch } = this.props; if (!deleteModal) { dispatch(deleteStatus(status.get('id'), withRedraft)); } else { - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage), - confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm), - onConfirm: () => dispatch(deleteStatus(status.get('id'), withRedraft)), - }, - })); + dispatch(openModal({ modalType: 'CONFIRM_DELETE_STATUS', modalProps: { statusId: status.get('id'), withRedraft } })); } }; handleEditClick = (status) => { - this.props.dispatch(editStatus(status.get('id'))); + const { dispatch, askReplyConfirmation } = this.props; + + if (askReplyConfirmation) { + dispatch(openModal({ modalType: 'CONFIRM_EDIT_STATUS', modalProps: { statusId: status.get('id') } })); + } else { + dispatch(editStatus(status.get('id'))); + } }; handleDirectClick = (account) => { diff --git a/app/javascript/flavours/glitch/features/ui/components/confirmation_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/confirmation_modal.jsx deleted file mode 100644 index 51a501595b..0000000000 --- a/app/javascript/flavours/glitch/features/ui/components/confirmation_modal.jsx +++ /dev/null @@ -1,83 +0,0 @@ -import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; - -import { injectIntl, FormattedMessage } from 'react-intl'; - -import { Button } from '../../../components/button'; - -class ConfirmationModal extends PureComponent { - - static propTypes = { - message: PropTypes.node.isRequired, - confirm: PropTypes.string.isRequired, - onClose: PropTypes.func.isRequired, - onConfirm: PropTypes.func.isRequired, - secondary: PropTypes.string, - onSecondary: PropTypes.func, - closeWhenConfirm: PropTypes.bool, - onDoNotAsk: PropTypes.func, - intl: PropTypes.object.isRequired, - }; - - static defaultProps = { - closeWhenConfirm: true, - }; - - handleClick = () => { - if (this.props.closeWhenConfirm) { - this.props.onClose(); - } - this.props.onConfirm(); - if (this.props.onDoNotAsk && this.doNotAskCheckbox.checked) { - this.props.onDoNotAsk(); - } - }; - - handleSecondary = () => { - this.props.onClose(); - this.props.onSecondary(); - }; - - handleCancel = () => { - this.props.onClose(); - }; - - setDoNotAskRef = (c) => { - this.doNotAskCheckbox = c; - }; - - render () { - const { message, confirm, secondary, onDoNotAsk } = this.props; - - return ( -
-
- {message} -
- -
- { onDoNotAsk && ( -
- - -
- )} -
- - {secondary !== undefined && ( -
-
-
- ); - } - -} - -export default injectIntl(ConfirmationModal); diff --git a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/clear_notifications.tsx b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/clear_notifications.tsx new file mode 100644 index 0000000000..6560446289 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/clear_notifications.tsx @@ -0,0 +1,46 @@ +import { useCallback } from 'react'; + +import { defineMessages, useIntl } from 'react-intl'; + +import { clearNotifications } from 'flavours/glitch/actions/notification_groups'; +import { useAppDispatch } from 'flavours/glitch/store'; + +import type { BaseConfirmationModalProps } from './confirmation_modal'; +import { ConfirmationModal } from './confirmation_modal'; + +const messages = defineMessages({ + clearTitle: { + id: 'notifications.clear_title', + defaultMessage: 'Clear notifications?', + }, + clearMessage: { + id: 'notifications.clear_confirmation', + defaultMessage: + 'Are you sure you want to permanently clear all your notifications?', + }, + clearConfirm: { + id: 'notifications.clear', + defaultMessage: 'Clear notifications', + }, +}); + +export const ConfirmClearNotificationsModal: React.FC< + BaseConfirmationModalProps +> = ({ onClose }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + const onConfirm = useCallback(() => { + void dispatch(clearNotifications()); + }, [dispatch]); + + return ( + + ); +}; diff --git a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/confirmation_modal.tsx b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/confirmation_modal.tsx new file mode 100644 index 0000000000..429850ef32 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/confirmation_modal.tsx @@ -0,0 +1,87 @@ +import { useCallback } from 'react'; + +import { FormattedMessage, defineMessages } from 'react-intl'; + +import { Button } from 'flavours/glitch/components/button'; + +export interface BaseConfirmationModalProps { + onClose: () => void; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars -- keep the message around while we find a place to show it +const messages = defineMessages({ + doNotAskAgain: { + id: 'confirmation_modal.do_not_ask_again', + defaultMessage: 'Do not ask for confirmation again', + }, +}); + +export const ConfirmationModal: React.FC< + { + title: React.ReactNode; + message: React.ReactNode; + confirm: React.ReactNode; + secondary?: React.ReactNode; + onSecondary?: () => void; + onConfirm: () => void; + closeWhenConfirm?: boolean; + } & BaseConfirmationModalProps +> = ({ + title, + message, + confirm, + onClose, + onConfirm, + secondary, + onSecondary, + closeWhenConfirm = true, +}) => { + const handleClick = useCallback(() => { + if (closeWhenConfirm) { + onClose(); + } + + onConfirm(); + }, [onClose, onConfirm, closeWhenConfirm]); + + const handleSecondary = useCallback(() => { + onClose(); + onSecondary?.(); + }, [onClose, onSecondary]); + + const handleCancel = useCallback(() => { + onClose(); + }, [onClose]); + + return ( +
+
+
+

{title}

+

{message}

+
+
+ +
+
+ {secondary && ( + <> + + +
+ + )} + + + + +
+
+
+ ); +}; diff --git a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/delete_list.tsx b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/delete_list.tsx new file mode 100644 index 0000000000..948b6c83da --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/delete_list.tsx @@ -0,0 +1,58 @@ +import { useCallback } from 'react'; + +import { defineMessages, useIntl } from 'react-intl'; + +import { useHistory } from 'react-router'; + +import { removeColumn } from 'flavours/glitch/actions/columns'; +import { deleteList } from 'flavours/glitch/actions/lists'; +import { useAppDispatch } from 'flavours/glitch/store'; + +import type { BaseConfirmationModalProps } from './confirmation_modal'; +import { ConfirmationModal } from './confirmation_modal'; + +const messages = defineMessages({ + deleteListTitle: { + id: 'confirmations.delete_list.title', + defaultMessage: 'Delete list?', + }, + deleteListMessage: { + id: 'confirmations.delete_list.message', + defaultMessage: 'Are you sure you want to permanently delete this list?', + }, + deleteListConfirm: { + id: 'confirmations.delete_list.confirm', + defaultMessage: 'Delete', + }, +}); + +export const ConfirmDeleteListModal: React.FC< + { + listId: string; + columnId: string; + } & BaseConfirmationModalProps +> = ({ listId, columnId, onClose }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + const history = useHistory(); + + const onConfirm = useCallback(() => { + dispatch(deleteList(listId)); + + if (columnId) { + dispatch(removeColumn(columnId)); + } else { + history.push('/lists'); + } + }, [dispatch, history, columnId, listId]); + + return ( + + ); +}; diff --git a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/delete_status.tsx b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/delete_status.tsx new file mode 100644 index 0000000000..e86cec10c0 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/delete_status.tsx @@ -0,0 +1,67 @@ +import { useCallback } from 'react'; + +import { defineMessages, useIntl } from 'react-intl'; + +import { deleteStatus } from 'flavours/glitch/actions/statuses'; +import { useAppDispatch } from 'flavours/glitch/store'; + +import type { BaseConfirmationModalProps } from './confirmation_modal'; +import { ConfirmationModal } from './confirmation_modal'; + +const messages = defineMessages({ + deleteAndRedraftTitle: { + id: 'confirmations.redraft.title', + defaultMessage: 'Delete & redraft post?', + }, + deleteAndRedraftMessage: { + id: 'confirmations.redraft.message', + defaultMessage: + 'Are you sure you want to delete this status and re-draft it? Favorites and boosts will be lost, and replies to the original post will be orphaned.', + }, + deleteAndRedraftConfirm: { + id: 'confirmations.redraft.confirm', + defaultMessage: 'Delete & redraft', + }, + deleteTitle: { + id: 'confirmations.delete.title', + defaultMessage: 'Delete post?', + }, + deleteMessage: { + id: 'confirmations.delete.message', + defaultMessage: 'Are you sure you want to delete this status?', + }, + deleteConfirm: { + id: 'confirmations.delete.confirm', + defaultMessage: 'Delete', + }, +}); + +export const ConfirmDeleteStatusModal: React.FC< + { + statusId: string; + withRedraft: boolean; + } & BaseConfirmationModalProps +> = ({ statusId, withRedraft, onClose }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + const onConfirm = useCallback(() => { + dispatch(deleteStatus(statusId, withRedraft)); + }, [dispatch, statusId, withRedraft]); + + return ( + + ); +}; diff --git a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/edit_status.tsx b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/edit_status.tsx new file mode 100644 index 0000000000..4a7e56c2a1 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/edit_status.tsx @@ -0,0 +1,45 @@ +import { useCallback } from 'react'; + +import { defineMessages, useIntl } from 'react-intl'; + +import { editStatus } from 'flavours/glitch/actions/statuses'; +import { useAppDispatch } from 'flavours/glitch/store'; + +import type { BaseConfirmationModalProps } from './confirmation_modal'; +import { ConfirmationModal } from './confirmation_modal'; + +const messages = defineMessages({ + editTitle: { + id: 'confirmations.edit.title', + defaultMessage: 'Overwrite post?', + }, + editConfirm: { id: 'confirmations.edit.confirm', defaultMessage: 'Edit' }, + editMessage: { + id: 'confirmations.edit.message', + defaultMessage: + 'Editing now will overwrite the message you are currently composing. Are you sure you want to proceed?', + }, +}); + +export const ConfirmEditStatusModal: React.FC< + { + statusId: string; + } & BaseConfirmationModalProps +> = ({ statusId, onClose }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + const onConfirm = useCallback(() => { + dispatch(editStatus(statusId)); + }, [dispatch, statusId]); + + return ( + + ); +}; diff --git a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/index.ts b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/index.ts new file mode 100644 index 0000000000..912c99a393 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/index.ts @@ -0,0 +1,8 @@ +export { ConfirmationModal } from './confirmation_modal'; +export { ConfirmDeleteStatusModal } from './delete_status'; +export { ConfirmDeleteListModal } from './delete_list'; +export { ConfirmReplyModal } from './reply'; +export { ConfirmEditStatusModal } from './edit_status'; +export { ConfirmUnfollowModal } from './unfollow'; +export { ConfirmClearNotificationsModal } from './clear_notifications'; +export { ConfirmLogOutModal } from './log_out'; diff --git a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/log_out.tsx b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/log_out.tsx new file mode 100644 index 0000000000..ac9439461f --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/log_out.tsx @@ -0,0 +1,40 @@ +import { useCallback } from 'react'; + +import { defineMessages, useIntl } from 'react-intl'; + +import { logOut } from 'flavours/glitch/utils/log_out'; + +import type { BaseConfirmationModalProps } from './confirmation_modal'; +import { ConfirmationModal } from './confirmation_modal'; + +const messages = defineMessages({ + logoutTitle: { id: 'confirmations.logout.title', defaultMessage: 'Log out?' }, + logoutMessage: { + id: 'confirmations.logout.message', + defaultMessage: 'Are you sure you want to log out?', + }, + logoutConfirm: { + id: 'confirmations.logout.confirm', + defaultMessage: 'Log out', + }, +}); + +export const ConfirmLogOutModal: React.FC = ({ + onClose, +}) => { + const intl = useIntl(); + + const onConfirm = useCallback(() => { + logOut(); + }, []); + + return ( + + ); +}; diff --git a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/reply.tsx b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/reply.tsx new file mode 100644 index 0000000000..415a453954 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/reply.tsx @@ -0,0 +1,46 @@ +import { useCallback } from 'react'; + +import { defineMessages, useIntl } from 'react-intl'; + +import { replyCompose } from 'flavours/glitch/actions/compose'; +import type { Status } from 'flavours/glitch/models/status'; +import { useAppDispatch } from 'flavours/glitch/store'; + +import type { BaseConfirmationModalProps } from './confirmation_modal'; +import { ConfirmationModal } from './confirmation_modal'; + +const messages = defineMessages({ + replyTitle: { + id: 'confirmations.reply.title', + defaultMessage: 'Overwrite post?', + }, + replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' }, + replyMessage: { + id: 'confirmations.reply.message', + defaultMessage: + 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?', + }, +}); + +export const ConfirmReplyModal: React.FC< + { + status: Status; + } & BaseConfirmationModalProps +> = ({ status, onClose }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + const onConfirm = useCallback(() => { + dispatch(replyCompose(status)); + }, [dispatch, status]); + + return ( + + ); +}; diff --git a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/unfollow.tsx b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/unfollow.tsx new file mode 100644 index 0000000000..fe6f5e362d --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/unfollow.tsx @@ -0,0 +1,50 @@ +import { useCallback } from 'react'; + +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; + +import { unfollowAccount } from 'flavours/glitch/actions/accounts'; +import type { Account } from 'flavours/glitch/models/account'; +import { useAppDispatch } from 'flavours/glitch/store'; + +import type { BaseConfirmationModalProps } from './confirmation_modal'; +import { ConfirmationModal } from './confirmation_modal'; + +const messages = defineMessages({ + unfollowTitle: { + id: 'confirmations.unfollow.title', + defaultMessage: 'Unfollow user?', + }, + unfollowConfirm: { + id: 'confirmations.unfollow.confirm', + defaultMessage: 'Unfollow', + }, +}); + +export const ConfirmUnfollowModal: React.FC< + { + account: Account; + } & BaseConfirmationModalProps +> = ({ account, onClose }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + const onConfirm = useCallback(() => { + dispatch(unfollowAccount(account.id)); + }, [dispatch, account.id]); + + return ( + @{account.acct} }} + /> + } + confirm={intl.formatMessage(messages.unfollowConfirm)} + onConfirm={onConfirm} + onClose={onClose} + /> + ); +}; diff --git a/app/javascript/flavours/glitch/features/ui/components/disabled_account_banner.jsx b/app/javascript/flavours/glitch/features/ui/components/disabled_account_banner.jsx index 396127829b..7a19983a9c 100644 --- a/app/javascript/flavours/glitch/features/ui/components/disabled_account_banner.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/disabled_account_banner.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; +import { FormattedMessage, injectIntl } from 'react-intl'; import { Link } from 'react-router-dom'; @@ -9,29 +9,15 @@ import { connect } from 'react-redux'; import { openModal } from 'flavours/glitch/actions/modal'; import { disabledAccountId, movedToAccountId, domain } from 'flavours/glitch/initial_state'; -import { logOut } from 'flavours/glitch/utils/log_out'; - -const messages = defineMessages({ - logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' }, - logoutConfirm: { id: 'confirmations.logout.confirm', defaultMessage: 'Log out' }, -}); const mapStateToProps = (state) => ({ disabledAcct: state.getIn(['accounts', disabledAccountId, 'acct']), movedToAcct: movedToAccountId ? state.getIn(['accounts', movedToAccountId, 'acct']) : undefined, }); -const mapDispatchToProps = (dispatch, { intl }) => ({ +const mapDispatchToProps = (dispatch) => ({ onLogout () { - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: intl.formatMessage(messages.logoutMessage), - confirm: intl.formatMessage(messages.logoutConfirm), - closeWhenConfirm: false, - onConfirm: () => logOut(), - }, - })); + dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT' })); }, }); diff --git a/app/javascript/flavours/glitch/features/ui/components/link_footer.jsx b/app/javascript/flavours/glitch/features/ui/components/link_footer.jsx index 7c0ece465f..fb07f9e549 100644 --- a/app/javascript/flavours/glitch/features/ui/components/link_footer.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/link_footer.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; +import { FormattedMessage, injectIntl } from 'react-intl'; import { Link } from 'react-router-dom'; @@ -11,24 +11,11 @@ import { openModal } from 'flavours/glitch/actions/modal'; import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { domain, version, source_url, statusPageUrl, profile_directory as profileDirectory } from 'flavours/glitch/initial_state'; import { PERMISSION_INVITE_USERS } from 'flavours/glitch/permissions'; -import { logOut } from 'flavours/glitch/utils/log_out'; -const messages = defineMessages({ - logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' }, - logoutConfirm: { id: 'confirmations.logout.confirm', defaultMessage: 'Log out' }, -}); - -const mapDispatchToProps = (dispatch, { intl }) => ({ +const mapDispatchToProps = (dispatch) => ({ onLogout () { - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: intl.formatMessage(messages.logoutMessage), - confirm: intl.formatMessage(messages.logoutConfirm), - closeWhenConfirm: false, - onConfirm: () => logOut(), - }, - })); + dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT' })); + }, }); diff --git a/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx b/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx index 063316ef1b..0bc393f7e8 100644 --- a/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx @@ -28,7 +28,16 @@ import ActionsModal from './actions_modal'; import AudioModal from './audio_modal'; import { BoostModal } from './boost_modal'; import BundleModalError from './bundle_modal_error'; -import ConfirmationModal from './confirmation_modal'; +import { + ConfirmationModal, + ConfirmDeleteStatusModal, + ConfirmDeleteListModal, + ConfirmReplyModal, + ConfirmEditStatusModal, + ConfirmUnfollowModal, + ConfirmClearNotificationsModal, + ConfirmLogOutModal, +} from './confirmation_modals'; import DeprecatedSettingsModal from './deprecated_settings_modal'; import DoodleModal from './doodle_modal'; import FavouriteModal from './favourite_modal'; @@ -47,6 +56,13 @@ export const MODAL_COMPONENTS = { 'FAVOURITE': () => Promise.resolve({ default: FavouriteModal }), 'DOODLE': () => Promise.resolve({ default: DoodleModal }), 'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }), + 'CONFIRM_DELETE_STATUS': () => Promise.resolve({ default: ConfirmDeleteStatusModal }), + 'CONFIRM_DELETE_LIST': () => Promise.resolve({ default: ConfirmDeleteListModal }), + 'CONFIRM_REPLY': () => Promise.resolve({ default: ConfirmReplyModal }), + 'CONFIRM_EDIT_STATUS': () => Promise.resolve({ default: ConfirmEditStatusModal }), + 'CONFIRM_UNFOLLOW': () => Promise.resolve({ default: ConfirmUnfollowModal }), + 'CONFIRM_CLEAR_NOTIFICATIONS': () => Promise.resolve({ default: ConfirmClearNotificationsModal }), + 'CONFIRM_LOG_OUT': () => Promise.resolve({ default: ConfirmLogOutModal }), 'MUTE': MuteModal, 'BLOCK': BlockModal, 'DOMAIN_BLOCK': DomainBlockModal, diff --git a/app/javascript/flavours/glitch/locales/en.json b/app/javascript/flavours/glitch/locales/en.json index 5ab92732ce..02e44932f8 100644 --- a/app/javascript/flavours/glitch/locales/en.json +++ b/app/javascript/flavours/glitch/locales/en.json @@ -34,10 +34,6 @@ "confirmations.missing_media_description.confirm": "Send anyway", "confirmations.missing_media_description.edit": "Edit media", "confirmations.missing_media_description.message": "At least one media attachment is lacking a description. Consider describing all media attachments for the visually impaired before sending your toot.", - "confirmations.unfilter.author": "Author", - "confirmations.unfilter.confirm": "Show", - "confirmations.unfilter.edit_filter": "Edit filter", - "confirmations.unfilter.filters": "Matching {count, plural, one {filter} other {filters}}", "direct.group_by_conversations": "Group by conversation", "endorsed_accounts_editor.endorsed_accounts": "Featured accounts", "favourite_modal.combo": "You can press {combo} to skip this next time", diff --git a/app/javascript/flavours/glitch/styles/components.scss b/app/javascript/flavours/glitch/styles/components.scss index aa96ef6b20..35342a1da4 100644 --- a/app/javascript/flavours/glitch/styles/components.scss +++ b/app/javascript/flavours/glitch/styles/components.scss @@ -6523,6 +6523,25 @@ a.status-card { } } + &__confirmation { + font-size: 14px; + line-height: 20px; + color: $darker-text-color; + + h1 { + font-size: 16px; + line-height: 24px; + color: $primary-text-color; + font-weight: 500; + margin-bottom: 8px; + } + + strong { + font-weight: 700; + color: $primary-text-color; + } + } + &__bullet-points { display: flex; flex-direction: column; @@ -6609,11 +6628,8 @@ a.status-card { .doodle-modal, .boost-modal, -.confirmation-modal, .report-modal, .actions-modal, -.mute-modal, -.block-modal, .compare-history-modal { background: lighten($ui-secondary-color, 8%); color: $inverted-text-color; @@ -6648,10 +6664,7 @@ a.status-card { } .doodle-modal__action-bar, -.boost-modal__action-bar, -.confirmation-modal__action-bar, -.mute-modal__action-bar, -.block-modal__action-bar { +.boost-modal__action-bar { display: flex; justify-content: space-between; align-items: center; @@ -6674,16 +6687,6 @@ a.status-card { } } -.mute-modal, -.block-modal { - line-height: 24px; -} - -.mute-modal .react-toggle, -.block-modal .react-toggle { - vertical-align: middle; -} - .report-modal { width: 90vw; max-width: 700px; @@ -7078,31 +7081,7 @@ a.status-card { } } -.confirmation-modal__action-bar, -.mute-modal__action-bar, -.block-modal__action-bar { - .confirmation-modal__secondary-button { - flex-shrink: 1; - } -} - -.confirmation-modal__secondary-button, -.confirmation-modal__cancel-button, -.mute-modal__cancel-button, -.block-modal__cancel-button { - background-color: transparent; - color: $lighter-text-color; - font-size: 14px; - font-weight: 500; - - &:hover, - &:focus, - &:active { - color: darken($lighter-text-color, 4%); - background-color: transparent; - } -} - +// TODO .confirmation-modal__do_not_ask_again { padding-inline-start: 20px; padding-inline-end: 20px; @@ -7115,9 +7094,6 @@ a.status-card { } } -.confirmation-modal__container, -.mute-modal__container, -.block-modal__container, .report-modal__target { padding: 30px; font-size: 16px; @@ -7151,31 +7127,10 @@ a.status-card { } } -.confirmation-modal__container, .report-modal__target { text-align: center; } -.block-modal, -.mute-modal { - &__explanation { - margin-top: 20px; - } - - .setting-toggle { - margin-top: 20px; - margin-bottom: 24px; - display: flex; - align-items: center; - - &__label { - color: $inverted-text-color; - margin: 0; - margin-inline-start: 8px; - } - } -} - .report-modal__target { padding: 15px;