From 6733c22f3f5f0cb668dc7c958c97485ef49c4748 Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Tue, 20 Aug 2024 11:17:17 +0800 Subject: [PATCH] Add site visibility settings confirmation modal (#50759) * Add confirmation modal for site visibility when changing from live to coming soon mode * Changelog * Remove unnecessary space * Update tests * Lint --- .../components/confirmation-modal.jsx | 116 +++++++++++ .../components/confirmation-modal.scss | 24 +++ .../test/confirmation-modal.test.js | 183 ++++++++++++++++++ .../launch-your-store/settings/slotfill.js | 24 +++ .../update-site-visibility-confirmation-modal | 4 + 5 files changed, 351 insertions(+) create mode 100644 plugins/woocommerce-admin/client/launch-your-store/settings/components/confirmation-modal.jsx create mode 100644 plugins/woocommerce-admin/client/launch-your-store/settings/components/confirmation-modal.scss create mode 100644 plugins/woocommerce-admin/client/launch-your-store/settings/components/test/confirmation-modal.test.js create mode 100644 plugins/woocommerce/changelog/update-site-visibility-confirmation-modal diff --git a/plugins/woocommerce-admin/client/launch-your-store/settings/components/confirmation-modal.jsx b/plugins/woocommerce-admin/client/launch-your-store/settings/components/confirmation-modal.jsx new file mode 100644 index 00000000000..3d1435af5a4 --- /dev/null +++ b/plugins/woocommerce-admin/client/launch-your-store/settings/components/confirmation-modal.jsx @@ -0,0 +1,116 @@ +/** + * External dependencies + */ +import React from 'react'; +import { Button, Modal } from '@wordpress/components'; +import { useState, useEffect } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import './confirmation-modal.scss'; + +/** + * Confirmation modal for the site visibility settings. + * + * @return {React.ReactNode} The confirmation modal component. + */ +export const ConfirmationModal = ( { + formRef, + saveButtonRef, + currentSetting, +} ) => { + const [ pendingSubmitEvent, setPendingSubmitEvent ] = useState( null ); + const [ isConfirmModalOpen, setIsConfirmModalOpen ] = useState( false ); + const currentComingSoon = currentSetting?.woocommerce_coming_soon ?? 'no'; + + // Hooks into settings' "mainform" to show a confirmation modal when the form is submitted. + useEffect( () => { + const form = formRef.current; + const handleFormSubmit = ( event ) => { + const formData = new FormData( form ); + + // Only block submission when switching to coming soon mode from live. + if ( + currentComingSoon === 'no' && + formData.get( 'woocommerce_coming_soon' ) === 'yes' + ) { + event.preventDefault(); + setIsConfirmModalOpen( true ); + setPendingSubmitEvent( event ); + } + }; + if ( form ) { + form.addEventListener( 'submit', handleFormSubmit ); + } + + return () => { + if ( form ) { + form.removeEventListener( 'submit', handleFormSubmit ); + } + }; + }, [ currentSetting, formRef ] ); + + const cancelSubmit = () => { + setPendingSubmitEvent( null ); // Clear the pending submit + setIsConfirmModalOpen( false ); // Close the modal + + if ( saveButtonRef.current ) { + saveButtonRef.current.classList.remove( 'is-busy' ); + } + }; + + const confirmSubmit = () => { + if ( pendingSubmitEvent ) { + // WooCommerce checks for the "save" input. + if ( saveButtonRef.current && formRef.current ) { + const hiddenInput = document.createElement( 'input' ); + hiddenInput.type = 'hidden'; + hiddenInput.name = saveButtonRef.current.name || 'save'; + hiddenInput.value = + saveButtonRef.current.value || + __( 'Save changes', 'woocommerce' ); + formRef.current.appendChild( hiddenInput ); + } + + pendingSubmitEvent.target.submit(); + setPendingSubmitEvent( null ); + } + setIsConfirmModalOpen( false ); // Close the modal + }; + + return isConfirmModalOpen ? ( + +
+ { __( + "Are you sure you want to switch from live to coming soon mode? Your site will not be visible, and customers won't be able to make purchases during this time.", + 'woocommerce' + ) } +
+
+
+
+
+ + +
+
+ ) : null; +}; diff --git a/plugins/woocommerce-admin/client/launch-your-store/settings/components/confirmation-modal.scss b/plugins/woocommerce-admin/client/launch-your-store/settings/components/confirmation-modal.scss new file mode 100644 index 00000000000..be45ad905a6 --- /dev/null +++ b/plugins/woocommerce-admin/client/launch-your-store/settings/components/confirmation-modal.scss @@ -0,0 +1,24 @@ +.site-visibility-settings-confirmation-modal { + .site-visibility-settings-confirmation-modal__content { + margin-top: 15px; + } + + .site-visibility-settings-confirmation-modal__buttons { + display: flex; + flex-flow: row-reverse; + } + + // Hacky solution for a divider line that spans over its container's paddings. + .divider-container { + height: 1px; + margin-bottom: 30px; + margin-top: 25px; + + & > hr { + position: absolute; + width: 100%; + left: 0; + right: 0; + } + } +} diff --git a/plugins/woocommerce-admin/client/launch-your-store/settings/components/test/confirmation-modal.test.js b/plugins/woocommerce-admin/client/launch-your-store/settings/components/test/confirmation-modal.test.js new file mode 100644 index 00000000000..d7267e32cf1 --- /dev/null +++ b/plugins/woocommerce-admin/client/launch-your-store/settings/components/test/confirmation-modal.test.js @@ -0,0 +1,183 @@ +/** + * External dependencies + */ +import React from 'react'; +import { render, fireEvent, screen } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; + +/** + * Internal dependencies + */ +import { ConfirmationModal } from '../confirmation-modal'; + +// Mock the necessary external dependencies +jest.mock( '@wordpress/components', () => ( { + Modal: jest.fn( ( { title, children, onRequestClose } ) => ( +
+
{ title }
+
{ children }
+ +
+ ) ), + Button: jest.fn( ( { children, onClick } ) => ( + + ) ), +} ) ); + +describe( 'ConfirmationModal', () => { + let formRef, saveButtonRef; + + const mockSelectComingSoon = ( value ) => { + // Set up form data + const input = document.createElement( 'input' ); + input.name = 'woocommerce_coming_soon'; + input.value = value; + formRef.current.appendChild( input ); + }; + + const fireSubmitEvent = () => { + // Simulate form submission + const submitEvent = new Event( 'submit', { + bubbles: true, + cancelable: true, + } ); + fireEvent( formRef.current, submitEvent ); + }; + + beforeEach( () => { + formRef = { current: document.createElement( 'form' ) }; + saveButtonRef = { current: document.createElement( 'button' ) }; + formRef.current.appendChild( saveButtonRef.current ); + document.body.appendChild( formRef.current ); + } ); + + afterEach( () => { + document.body.removeChild( formRef.current ); + } ); + + it( 'should prompt the modal if current setting is live and submit the form', () => { + const currentSetting = { woocommerce_coming_soon: 'no' }; + + render( + + ); + + const submitListener = jest.fn(); + formRef.current.onsubmit = submitListener; + + mockSelectComingSoon( 'yes' ); + fireSubmitEvent(); + + // Confirm modal is prompted + expect( + screen.getByText( 'Confirm switch to ‘Coming soon’ mode' ) + ).toBeInTheDocument(); + + // Simulate confirming submission + fireEvent.click( screen.getByText( 'Switch' ) ); + + // Ensure the form is submitted + expect( submitListener ).toHaveBeenCalled(); + } ); + + it( 'should prompt the modal if current setting is not set', () => { + render( + + ); + + mockSelectComingSoon( 'yes' ); + fireSubmitEvent(); + + // Confirm that the modal is not prompted + expect( + screen.queryByText( 'Confirm switch to ‘Coming soon’ mode' ) + ).toBeInTheDocument(); + } ); + + it( 'should not prompt the modal if current setting is already "coming soon"', () => { + const currentSetting = { woocommerce_coming_soon: 'yes' }; + + render( + + ); + + mockSelectComingSoon( 'yes' ); + fireSubmitEvent(); + + // Confirm that the modal is not prompted + expect( + screen.queryByText( 'Confirm switch to ‘Coming soon’ mode' ) + ).not.toBeInTheDocument(); + } ); + + it( 'should close the modal on cancel', () => { + const currentSetting = { woocommerce_coming_soon: 'no' }; + + render( + + ); + + mockSelectComingSoon( 'yes' ); + fireSubmitEvent(); + + // Confirm modal is prompted + expect( + screen.getByText( 'Confirm switch to ‘Coming soon’ mode' ) + ).toBeInTheDocument(); + + // Simulate canceling the modal + fireEvent.click( screen.getByText( 'Cancel' ) ); + + // Confirm that the modal is closed + expect( + screen.queryByText( 'Confirm switch to ‘Coming soon’ mode' ) + ).not.toBeInTheDocument(); + } ); + + it( 'should handle the save button correctly', () => { + const currentSetting = { woocommerce_coming_soon: 'no' }; + saveButtonRef.current.name = 'save'; + saveButtonRef.current.value = 'Save changes'; + + render( + + ); + + mockSelectComingSoon( 'yes' ); + fireSubmitEvent(); + + // Confirm modal is prompted + expect( + screen.getByText( 'Confirm switch to ‘Coming soon’ mode' ) + ).toBeInTheDocument(); + + // Simulate confirming submission + fireEvent.click( screen.getByText( 'Switch' ) ); + + // Check that the hidden input with "save" has been added to the form + const hiddenInput = + formRef.current.querySelector( 'input[name="save"]' ); + expect( hiddenInput ).toBeInTheDocument(); + expect( hiddenInput.value ).toBe( 'Save changes' ); + } ); +} ); diff --git a/plugins/woocommerce-admin/client/launch-your-store/settings/slotfill.js b/plugins/woocommerce-admin/client/launch-your-store/settings/slotfill.js index aba9bd41ee6..776579e4101 100644 --- a/plugins/woocommerce-admin/client/launch-your-store/settings/slotfill.js +++ b/plugins/woocommerce-admin/client/launch-your-store/settings/slotfill.js @@ -12,6 +12,7 @@ import { createInterpolateElement, createElement, useEffect, + useRef, } from '@wordpress/element'; import { registerPlugin } from '@wordpress/plugins'; import { __ } from '@wordpress/i18n'; @@ -29,6 +30,7 @@ import { COMING_SOON_PAGE_EDITOR_LINK, SITE_VISIBILITY_DOC_LINK, } from '../constants'; +import { ConfirmationModal } from './components/confirmation-modal'; const { Fill } = createSlotFill( SETTINGS_SLOT_FILL_CONSTANT ); @@ -45,6 +47,21 @@ const SiteVisibility = () => { const [ privateLink, setPrivateLink ] = useState( setting?.woocommerce_private_link || 'no' ); + const formRef = useRef( null ); + const saveButtonRef = useRef( null ); + + useEffect( () => { + const saveButton = document.getElementsByClassName( + 'woocommerce-save-button' + )[ 0 ]; + if ( saveButton ) { + saveButtonRef.current = saveButton; + } + const form = document.querySelector( '#mainform' ); + if ( form ) { + formRef.current = form; + } + }, [] ); useEffect( () => { const initValues = { @@ -266,6 +283,13 @@ const SiteVisibility = () => { ) }

+ { formRef.current && saveButtonRef.current ? ( + + ) : null } ); }; diff --git a/plugins/woocommerce/changelog/update-site-visibility-confirmation-modal b/plugins/woocommerce/changelog/update-site-visibility-confirmation-modal new file mode 100644 index 00000000000..341536f2744 --- /dev/null +++ b/plugins/woocommerce/changelog/update-site-visibility-confirmation-modal @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Add confirmation prompt for site visibility settings when changing from live to coming soon mode