diff --git a/lib/shared/thread-utils.js b/lib/shared/thread-utils.js --- a/lib/shared/thread-utils.js +++ b/lib/shared/thread-utils.js @@ -1605,6 +1605,22 @@ }, [threadInfo]); } +function communityOrThreadNoun(threadInfo: RawThreadInfo | ThreadInfo): string { + return threadTypeIsCommunityRoot(threadInfo.type) + ? 'community' + : threadNoun(threadInfo.type, threadInfo.parentThreadID); +} + +function getThreadsToDeleteText( + threadInfo: RawThreadInfo | ThreadInfo, +): string { + return `${ + threadTypeIsCommunityRoot(threadInfo.type) + ? 'Subchannels and threads' + : 'Threads' + } within this ${communityOrThreadNoun(threadInfo)}`; +} + export { threadHasPermission, viewerIsMember, @@ -1672,4 +1688,5 @@ threadInfoInsideCommunity, useRoleMemberCountsForCommunity, useRoleUserSurfacedPermissions, + getThreadsToDeleteText, }; diff --git a/web/modals/threads/settings/thread-settings-delete-confirmation-modal.css b/web/modals/threads/settings/thread-settings-delete-confirmation-modal.css new file mode 100644 --- /dev/null +++ b/web/modals/threads/settings/thread-settings-delete-confirmation-modal.css @@ -0,0 +1,14 @@ +.container { + padding: 0 40px 32px; + border-radius: 8px; + color: var(--modal-fg); +} +.text { + font-size: var(--xl-font-20); + padding: 5px 0px 20px; +} +.buttonContainer { + display: flex; + justify-content: flex-end; + gap: 24px; +} diff --git a/web/modals/threads/settings/thread-settings-delete-confirmation-modal.react.js b/web/modals/threads/settings/thread-settings-delete-confirmation-modal.react.js new file mode 100644 --- /dev/null +++ b/web/modals/threads/settings/thread-settings-delete-confirmation-modal.react.js @@ -0,0 +1,54 @@ +// @flow + +import * as React from 'react'; + +import { useModalContext } from 'lib/components/modal-provider.react.js'; +import { getThreadsToDeleteText } from 'lib/shared/thread-utils.js'; +import type { ThreadInfo } from 'lib/types/thread-types'; + +import css from './thread-settings-delete-confirmation-modal.css'; +import Button from '../../../components/button.react.js'; +import Modal from '../../modal.react.js'; + +type BaseProps = { + +threadInfo: ThreadInfo, + +onConfirmation: () => mixed, +}; + +function ThreadDeleteConfirmationModal({ + threadInfo, + onConfirmation, +}: BaseProps): React.Node { + const { popModal } = useModalContext(); + const threadsToDeleteText = React.useMemo( + () => getThreadsToDeleteText(threadInfo), + [threadInfo], + ); + + return ( + + + + {threadsToDeleteText} will also be permanently deleted. Are you sure + you want to continue? + + + + No + + + Yes + + + + + ); +} + +export default ThreadDeleteConfirmationModal; diff --git a/web/modals/threads/settings/thread-settings-delete-tab.react.js b/web/modals/threads/settings/thread-settings-delete-tab.react.js --- a/web/modals/threads/settings/thread-settings-delete-tab.react.js +++ b/web/modals/threads/settings/thread-settings-delete-tab.react.js @@ -8,6 +8,7 @@ } from 'lib/actions/thread-actions.js'; import { useModalContext } from 'lib/components/modal-provider.react.js'; import SWMansionIcon from 'lib/components/SWMansionIcon.react.js'; +import { containedThreadInfos } from 'lib/selectors/thread-selectors.js'; import { type SetState } from 'lib/types/hook-types.js'; import { type ThreadInfo } from 'lib/types/thread-types.js'; import { @@ -16,8 +17,10 @@ } from 'lib/utils/action-utils.js'; import SubmitSection from './submit-section.react.js'; +import ThreadDeleteConfirmationModal from './thread-settings-delete-confirmation-modal.react.js'; import css from './thread-settings-delete-tab.css'; import { buttonThemes } from '../../../components/button.react.js'; +import { useSelector } from '../../../redux/redux-utils.js'; type ThreadSettingsDeleteTabProps = { +threadSettingsOperationInProgress: boolean, @@ -39,14 +42,28 @@ const modalContext = useModalContext(); const dispatchActionPromise = useDispatchActionPromise(); const callDeleteThread = useServerCall(deleteThread); + const containedThreads = useSelector( + state => containedThreadInfos(state)[threadInfo.id], + ); + const shouldUseDeleteConfirmationModal = React.useMemo( + () => containedThreads?.length > 0, + [containedThreads?.length], + ); + const popThreadDeleteConfirmationModal = React.useCallback(() => { + if (shouldUseDeleteConfirmationModal) { + modalContext.popModal(); + } + }, [modalContext, shouldUseDeleteConfirmationModal]); const deleteThreadAction = React.useCallback(async () => { try { setErrorMessage(''); const response = await callDeleteThread(threadInfo.id); + popThreadDeleteConfirmationModal(); modalContext.popModal(); return response; } catch (e) { + popThreadDeleteConfirmationModal(); setErrorMessage( e.message === 'invalid_credentials' ? 'permission not granted' @@ -54,14 +71,36 @@ ); throw e; } - }, [callDeleteThread, modalContext, setErrorMessage, threadInfo.id]); - + }, [ + callDeleteThread, + modalContext, + popThreadDeleteConfirmationModal, + setErrorMessage, + threadInfo.id, + ]); + const dispatchDeleteThreadAction = React.useCallback(() => { + dispatchActionPromise(deleteThreadActionTypes, deleteThreadAction()); + }, [dispatchActionPromise, deleteThreadAction]); const onDelete = React.useCallback( (event: SyntheticEvent) => { event.preventDefault(); - dispatchActionPromise(deleteThreadActionTypes, deleteThreadAction()); + if (shouldUseDeleteConfirmationModal) { + modalContext.pushModal( + , + ); + } else { + dispatchDeleteThreadAction(); + } }, - [deleteThreadAction, dispatchActionPromise], + [ + dispatchDeleteThreadAction, + modalContext, + shouldUseDeleteConfirmationModal, + threadInfo, + ], ); return (
+ {threadsToDeleteText} will also be permanently deleted. Are you sure + you want to continue? +