diff --git a/includes/Data/Options.php b/includes/Data/Options.php index 9cb9abe37..a19fb9fc6 100644 --- a/includes/Data/Options.php +++ b/includes/Data/Options.php @@ -30,6 +30,7 @@ final class Options { 'settings_initialized' => 'settings_initialized', 'plugins_init_status' => 'plugins_init_status', 'plugin_install_queue' => 'plugin_install_queue', + 'plugin_uninstall_queue' => 'plugin_uninstall_queue', 'flow' => 'flow', 'theme_init_status' => 'theme_init_status', 'theme_install_queue' => 'theme_install_queue', diff --git a/includes/Data/Plugins.php b/includes/Data/Plugins.php index 1cbe923e9..711c1a4eb 100644 --- a/includes/Data/Plugins.php +++ b/includes/Data/Plugins.php @@ -41,6 +41,10 @@ final class Plugins { 'approved' => true, 'path' => 'yith-woocommerce-ajax-search/init.php', ), + 'creative-mail-by-constant-contact' => array( + 'approved' => true, + 'path' => 'creative-mail-by-constant-contact/creative-mail-plugin.php', + ), ); /** @@ -229,6 +233,18 @@ public static function get() { ); } + /** + * Use this for finding the path for installed plugins. + * + * @return array + */ + public static function get_squashed() { + return array_merge( + array_filter( self::$wp_slugs, array( __CLASS__, 'check_approved' ) ) , + array_filter( self::$nfd_slugs, array( __CLASS__, 'check_approved' ) ) , + ); + } + /** * Get approved slugs/urls/domains * diff --git a/includes/Data/SiteFeatures.php b/includes/Data/SiteFeatures.php new file mode 100644 index 000000000..b16d2fc1c --- /dev/null +++ b/includes/Data/SiteFeatures.php @@ -0,0 +1,115 @@ + array(), + 'ecommerce' => array( + 'jetpack' => array( + 'slug' => 'jetpack', + 'icon' => '--site-features-security', + 'title' => 'Security, Speed & Growth', + 'subtitle' => 'Powered by Jetpack', + 'desc' => 'Jetpack', + 'selected' => false, + ), + 'wpforms-lite' => array( + 'slug' => 'wpforms-lite', + 'icon' => '--site-features-form', + 'title' => 'Forms', + 'subtitle' => 'Powered by WP Forms', + 'desc' => 'WP Forms', + 'selected' => false, + ), + 'google-analytics-for-wordpress' => array( + 'slug' => 'google-analytics-for-wordpress', + 'icon' => '--site-features-analytics', + 'title' => 'Site Traffic', + 'subtitle' => 'Powered by MonsterInsights', + 'desc' => 'MonsterInsights', + 'selected' => false, + ), + 'wordpress-seo' => array( + 'slug' => 'wordpress-seo', + 'icon' => '--site-features-share', + 'title' => 'Search Engine Optimization', + 'subtitle' => 'Powered by Yoast', + 'desc' => 'Yoast', + 'selected' => false, + ), + 'creative-mail-by-constant-contact' => array( + 'slug' => 'creative-mail-by-constant-contact', + 'icon' => '--site-features-email', + 'title' => 'Email Newsletters', + 'subtitle' => 'Powered by Creative Email', + 'desc' => 'Creative Email', + 'selected' => false, + ), + 'yith-woocommerce-ajax-search' => array( + 'slug' => 'yith-woocommerce-ajax-search', + 'icon' => '--site-features-search', + 'title' => 'Enhanced Product Search', + 'subtitle' => 'Powered by YITH', + 'desc' => 'YITH', + 'selected' => false, + ), + 'nfd_slug_yith_woocommerce_ajax_product_filter' => array( + 'slug' => 'nfd_slug_yith_woocommerce_ajax_product_filter', + 'icon' => '--site-features-filter', + 'title' => 'Enhanced Product Filters', + 'subtitle' => 'Powered by YITH', + 'desc' => 'YITH', + 'selected' => false, + ), + 'nfd_slug_yith_woocommerce_booking' => array( + 'slug' => 'nfd_slug_yith_woocommerce_booking', + 'icon' => '--site-features-bookingcalendar', + 'title' => 'Bookings & Appointments', + 'subtitle' => 'Powered by YITH', + 'desc' => 'YITH', + 'selected' => false, + ), + 'nfd_slug_yith_woocommerce_wishlist' => array( + 'slug' => 'nfd_slug_yith_woocommerce_wishlist', + 'icon' => '--site-features-wishlist', + 'title' => 'Product Wishlists', + 'subtitle' => 'Powered by YITH', + 'desc' => 'YITH', + 'selected' => false, + ), + 'optinmonster' => array( + 'slug' => 'optinmonster', + 'icon' => '--site-features-lead', + 'title' => 'Lead Generation', + 'subtitle' => 'Powered by Optin Monster', + 'desc' => 'Optin Monster', + 'selected' => false, + ), + ), + ); + + public static function mark_initial_plugins() { + $flow = Data::current_flow(); + $installed_plugins = Plugins::get_init(); + + // Get a Copy of the list for alteration + $site_features_marked = self::$site_features[ $flow ]; + + foreach ( $installed_plugins as $installed_plugin ) { + if ( isset( $site_features_marked[ $installed_plugin['slug'] ] ) ) { + $site_features_marked[ $installed_plugin['slug'] ]['selected'] = true; + } + } + + return $site_features_marked; + } + + public static function get() { + return self::mark_initial_plugins(); + } + +} diff --git a/includes/RestApi/PluginsController.php b/includes/RestApi/PluginsController.php index b2a9197f3..039c4dfb8 100644 --- a/includes/RestApi/PluginsController.php +++ b/includes/RestApi/PluginsController.php @@ -3,10 +3,12 @@ use NewfoldLabs\WP\Module\Onboarding\Permissions; use NewfoldLabs\WP\Module\Onboarding\Data\Plugins; -use NewfoldLabs\WP\Module\Onboarding\Data\Options; +use NewfoldLabs\WP\Module\Onboarding\Data\SiteFeatures; use NewfoldLabs\WP\Module\Onboarding\Services\PluginInstaller; use NewfoldLabs\WP\Module\Onboarding\Tasks\PluginInstallTask; use NewfoldLabs\WP\Module\Onboarding\TaskManagers\PluginInstallTaskManager; +use NewfoldLabs\WP\Module\Onboarding\Tasks\PluginUninstallTask; +use NewfoldLabs\WP\Module\Onboarding\TaskManagers\PluginUninstallTaskManager; /** * Class PluginsController @@ -71,13 +73,31 @@ public function register_routes() { $this->rest_base . '/status', array( array( - 'methods' => \WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_status' ), - 'args' => $this->get_status_args(), + 'methods' => \WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_status' ), + 'args' => $this->get_status_args(), 'permission_callback' => array( Permissions::class, 'rest_is_authorized_admin' ), ), ) ); + + \register_rest_route( + $this->namespace, + $this->rest_base . '/site-features', + array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_site_features' ), + 'permission_callback' => array( Permissions::class, 'rest_is_authorized_admin' ), + ), + array( + 'methods' => \WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'set_site_features' ), + 'args' => $this->set_site_features_args(), + 'permission_callback' => array( $this, 'check_install_permissions' ), + ), + ) + ); } /** @@ -132,6 +152,15 @@ public function get_status_args() { ); } + public function set_site_features_args() { + return array( + 'plugins' => array( + 'type' => 'object', + 'required' => true, + ), + ); + } + /** * Verify caller has permissions to install plugins. * @@ -242,4 +271,57 @@ public function get_status( \WP_REST_Request $request ) { ); } + + /** + * Retrieves the Customized list of Plugins for the user. + * + * @return array|\WP_Error + */ + public function get_site_features() { + return SiteFeatures::get(); + } + + /** + * Installs/Uninstalls the requested plugins. + * + * @param \WP_REST_Request $request + * + * @return \WP_REST_Response|\WP_Error + */ + public function set_site_features( \WP_REST_Request $request ) { + + $plugin_body = json_decode( $request->get_body(), true ); + $plugins = isset( $plugin_body['plugins'] ) ? $plugin_body['plugins'] : false; + + if ( ! $plugins ) { + return new \WP_Error( + 'plugin_list_not_provided', + 'Plugins List Not Provided', + array( 'status' => 404 ) + ); + } + + foreach ( $plugins as $plugin => $decision ) { + if ( $decision ) { + PluginInstallTaskManager::add_to_queue( + new PluginInstallTask( + $plugin, + true, + ) + ); + } else { + PluginUninstallTaskManager::add_to_queue( + new PluginUninstallTask( + $plugin, + ) + ); + } + } + + return new \WP_REST_Response( + array(), + 202 + ); + } } + diff --git a/includes/Services/PluginUninstaller.php b/includes/Services/PluginUninstaller.php new file mode 100644 index 000000000..9b5c4406e --- /dev/null +++ b/includes/Services/PluginUninstaller.php @@ -0,0 +1,158 @@ + 500 ) + ); + } + + // Removes directory and files of a plugin + $deleted = \delete_plugins( array( $plugin_path ) ); + if ( ! $deleted || is_wp_error( $deleted ) ) { + return new \WP_Error( + 'nfd_onboarding_error', + 'Unable to Delete the Plugin', + array( 'status' => 500 ) + ); + } + + return true; + } + +} diff --git a/includes/TaskManagers/PluginInstallTaskManager.php b/includes/TaskManagers/PluginInstallTaskManager.php index 283c191b6..c03014281 100644 --- a/includes/TaskManagers/PluginInstallTaskManager.php +++ b/includes/TaskManagers/PluginInstallTaskManager.php @@ -95,6 +95,9 @@ public function install() { priority at the beginning of the array */ $plugin_to_install = array_shift( $plugins ); + // Update the plugin install queue. + \update_option( Options::get_option_name( self::$queue_name ), $plugins ); + // Recreate the PluginInstall task from the associative array. $plugin_install_task = new PluginInstallTask( $plugin_to_install['slug'], @@ -113,21 +116,26 @@ public function install() { // If there is an error, then increase the retry count for the task. $plugin_install_task->increment_retries(); + // Get Latest Value of the install queue + $plugins = \get_option( Options::get_option_name( self::$queue_name ), array() ); + /* If the number of retries have not exceeded the limit then re-queue the task at the end of the queue to be retried. */ if ( $plugin_install_task->get_retries() <= self::$retry_limit ) { - array_push( $plugins, $plugin_install_task->to_array() ); + array_push( $plugins, $plugin_install_task->to_array() ); + + // Update the plugin install queue. + \update_option( Options::get_option_name( self::$queue_name ), $plugins ); } } // If there are no more plugins to be installed then change the status to completed. if ( empty( $plugins ) ) { - \update_option( Options::get_option_name( 'plugins_init_status' ), 'completed' ); + return \update_option( Options::get_option_name( 'plugins_init_status' ), 'completed' ); } - // Update the plugin install queue. - return \update_option( Options::get_option_name( self::$queue_name ), $plugins ); + return true; } /** @@ -166,6 +174,24 @@ public static function add_to_queue( PluginInstallTask $plugin_install_task ) { return \update_option( Options::get_option_name( self::$queue_name ), $queue->to_array() ); } + public static function remove_from_queue( $plugin ) { + /* + Get the plugins queued up to be installed, the PluginInstall task gets + converted to an associative array before storing it in the option. */ + $plugins = \get_option( Options::get_option_name( self::$queue_name ), array() ); + + $queue = new PriorityQueue(); + foreach ( $plugins as $queued_plugin ) { + /* + If the Plugin slug does not match add it back to the queue. */ + if ( $queued_plugin['slug'] !== $plugin ) { + $queue->insert( $queued_plugin, $queued_plugin['priority'] ); + } + } + + return \update_option( Options::get_option_name( self::$queue_name ), $queue->to_array() ); + } + public static function status( $plugin ) { $plugins = \get_option( Options::get_option_name( self::$queue_name ), array() ); return array_search( $plugin, array_column( $plugins, 'slug' ) ); diff --git a/includes/TaskManagers/PluginUninstallTaskManager.php b/includes/TaskManagers/PluginUninstallTaskManager.php new file mode 100644 index 000000000..74ea563fd --- /dev/null +++ b/includes/TaskManagers/PluginUninstallTaskManager.php @@ -0,0 +1,137 @@ + 10, + 'display' => __( 'Once Every Ten Seconds' ), + ); + } + + return $schedules; + } + + /** + + * Queue out a PluginUninstallTask with the highest priority in the plugin uninstall queue and execute it. + * + * @return array|false + */ + public function uninstall() { + /* + Get the plugins queued up to be uninstalled, the PluginUninstall task gets + converted to an associative array before storing it in the option. */ + $plugins = \get_option( Options::get_option_name( self::$queue_name ), array() ); + + /* + Conversion of the max heap to an array will always place the PluginUninstallTask with the highest + priority at the beginning of the array */ + $plugin_to_uninstall = array_shift( $plugins ); + + // Update the plugin uninstall queue. + \update_option( Options::get_option_name( self::$queue_name ), $plugins ); + + // Recreate the PluginInstall task from the associative array. + $plugin_uninstall_task = new PluginUninstallTask( + $plugin_to_uninstall['slug'], + $plugin_to_uninstall['priority'], + $plugin_to_uninstall['retries'] + ); + + // Execute the PluginUninstall Task. + $status = $plugin_uninstall_task->execute(); + if ( \is_wp_error( $status ) ) { + + // If there is an error, then increase the retry count for the task. + $plugin_uninstall_task->increment_retries(); + + /* + If the number of retries have not exceeded the limit + then re-queue the task at the end of the queue to be retried. */ + if ( $plugin_uninstall_task->get_retries() <= self::$retry_limit ) { + array_push( $plugins, $plugin_uninstall_task->to_array() ); + + // Update the plugin install queue. + \update_option( Options::get_option_name( self::$queue_name ), $plugins ); + } + } + + return true; + } + + /** + * @param PluginUninstallTask $plugin_uninstall_task + * + * Adds a new PluginUninstallTask to the Plugin Uninstall queue. + * The Task will be inserted at an appropriate position in the queue based on it's priority. + * + * @return array|false + */ + public static function add_to_queue( PluginUninstallTask $plugin_uninstall_task ) { + /* + Get the plugins queued up to be uninstalled, the PluginUninstall task gets + converted to an associative array before storing it in the option. */ + $plugins = \get_option( Options::get_option_name( self::$queue_name ), array() ); + + $queue = new PriorityQueue(); + foreach ( $plugins as $queued_plugin ) { + + /* + Check if there is an already existing PluginUninstallTask in the queue + for a given slug. */ + if ( $queued_plugin['slug'] === $plugin_uninstall_task->get_slug() ) { + return false; + } + $queue->insert( $queued_plugin, $queued_plugin['priority'] ); + } + + // Insert a new PluginUninstallTask at the appropriate position in the queue. + $queue->insert( + $plugin_uninstall_task->to_array(), + $plugin_uninstall_task->get_priority() + ); + + return \update_option( Options::get_option_name( self::$queue_name ), $queue->to_array() ); + } + + public static function status( $plugin ) { + $plugins = \get_option( Options::get_option_name( self::$queue_name ), array() ); + return array_search( $plugin, array_column( $plugins, 'slug' ) ); + } +} diff --git a/includes/TaskManagers/TaskManager.php b/includes/TaskManagers/TaskManager.php index 5b394a941..e4b816b6c 100644 --- a/includes/TaskManagers/TaskManager.php +++ b/includes/TaskManagers/TaskManager.php @@ -7,6 +7,7 @@ final class TaskManager { protected $task_managers = array( 'NewfoldLabs\\WP\Module\\Onboarding\\TaskManagers\\PluginInstallTaskManager', + 'NewfoldLabs\\WP\Module\\Onboarding\\TaskManagers\\PluginUninstallTaskManager', 'NewfoldLabs\\WP\Module\\Onboarding\\TaskManagers\\ThemeInstallTaskManager', ); diff --git a/includes/Tasks/PluginUninstallTask.php b/includes/Tasks/PluginUninstallTask.php new file mode 100644 index 000000000..a0780cdfb --- /dev/null +++ b/includes/Tasks/PluginUninstallTask.php @@ -0,0 +1,62 @@ +slug = $slug; + $this->priority = $priority; + $this->retries = $retries; + } + + public function get_slug() { + return $this->slug; + } + + public function get_priority() { + return $this->priority; + } + + public function get_retries() { + return $this->retries; + } + + public function increment_retries() { + $this->retries++; + } + + /** + * Uninstalls the Plugin using the PluginUninstaller Service. + * + * @return \WP_REST_Response|\WP_Error + */ + public function execute() { + return PluginUninstaller::uninstall( $this->get_slug() ); + } + + /** + * Convert the PluginUninstallTask into an associative array. + * + * @return array + */ + public function to_array() { + return array( + 'slug' => $this->slug, + 'priority' => $this->priority, + 'retries' => $this->retries, + ); + } + +} diff --git a/src/OnboardingSPA/components/CheckboxTemplate/CheckboxItem/index.js b/src/OnboardingSPA/components/CheckboxTemplate/CheckboxItem/index.js new file mode 100644 index 000000000..7681fb09e --- /dev/null +++ b/src/OnboardingSPA/components/CheckboxTemplate/CheckboxItem/index.js @@ -0,0 +1,111 @@ +import { useState } from '@wordpress/element'; +import { Icon, help, box } from '@wordpress/icons'; + +import { CheckboxControl } from '@wordpress/components'; + +/** + * Checkbox Item Component + * This returns a Single Element with a toggable description + * + * @param {string} icon - The icon name of the Item + * @param {string} title - The Main Title of the Item + * @param {string} subtitle - The Sub Title of the Item + * @param {string} desc - The Description of the Item + * + * @return CheckboxItem + */ + +const CheckboxItem = ( { + name, + icon, + title, + desc, + subtitle, + callback, + isSelectedDefault, + className = 'checkbox-item', +} ) => { + const [ showDescription, setShowDescription ] = useState( false ); + const [ isSelected, setIsSelected ] = useState( isSelectedDefault ); + + const handleCheck = () => { + setIsSelected( ! isSelected ); + callback( name, ! isSelected ); + }; + + const handleShowDesc = () => { + setShowDescription( ! showDescription ); + }; + + return ( +
+
+
+ +
+
+
+
+
+
+ {title} +
+
+ {subtitle} +
+
+
+ +
+
+
+
+ { + showDescription && ( +
{desc}
+ ) + } +
+ ); +}; + +export default CheckboxItem; diff --git a/src/OnboardingSPA/components/CheckboxTemplate/CheckboxItem/stylesheet.scss b/src/OnboardingSPA/components/CheckboxTemplate/CheckboxItem/stylesheet.scss new file mode 100644 index 000000000..54fdfd435 --- /dev/null +++ b/src/OnboardingSPA/components/CheckboxTemplate/CheckboxItem/stylesheet.scss @@ -0,0 +1,122 @@ +/*COLOR VARIABLES*/ +$white-offset: rgb(224, 224, 224); +$main-color-dark: var(--wp-admin-theme-color); +$main-color-light: var(--nfd-onboarding-white); +$main-color: var(--nfd-onboarding-highlighted--rgb); +$main-border-main: var(--nfd-onboarding-primary-alt); +$box-shadow: var(--nfd-onboarding-light-gray-highlighted); + +.checkbox-item { + margin: 12px; + padding: 16px; + margin-top: 20px; + background: $main-color-light; + border: 1px solid $white-offset; + width: clamp(15rem, 25vw, 35rem); + box-shadow: 0px 2px 8px 2px rgba(204, 204, 204, 0.175295); + + &-container{ + display: flex; + align-items: center; + justify-content: flex-start; + } + + &-checkbox{ + padding: 6px; + display: flex; + align-items: center; + justify-content: center; + } + + &__contents{ + width: 100%; + display: flex; + align-items: center; + justify-content: center; + + &-icon{ + width: 45px; + height: 45px; + display: flex; + margin-right: 16px; + border-radius: 50%; + align-items: center; + background: #F0F0F0; + justify-content: center; + + &--selected { + background: $main-color-dark !important; + } + + &--shown { + background: #F0F0F0; + } + } + + &-text { + flex: 1; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: center; + + &-title{ + color: #0D0D0D; + line-height: 18px; + margin-bottom: 6px; + font-size: clamp(0.9rem, 2vw, 1rem); + + &--selected { + color: $main-color-dark; + } + } + + &-subtitle { + color: #343434; + font-weight: 200; + line-height: 18px; + font-size: clamp(0.82rem, 2vw, 0.9rem); + } + } + + &-help { + cursor: pointer; + } + + } + + &--selected { + background: rgba($main-color, 0.2); + border: 1px solid rgba($main-color, 0.6); + box-shadow: 0px 2px 8px 2px rgba($white-offset, 0.8); + } + + &--shown { + border-bottom: none; + background: $box-shadow; + border-radius: 2px 2px 0px 0px; + border-top: 1px solid rgba($main-color, 0.1); + border-left: 1px solid rgba($main-color, 0.1); + border-right: 1px solid rgba($main-color, 0.1); + } + + &__desc { + z-index: 2; + padding: 16px; + border-top: none; + margin-left: 12px; + margin-top: -12px; + position: absolute; + font-style: italic; + background: $box-shadow; + border-radius: 0px 0px 2px 2px; + transform-origin: top center; + width: clamp(15rem, 25vw, 35rem); + font-size: clamp(0.82rem, 2vw, 0.9rem); + border-left: 1px solid rgba($main-color, 0.1); + border-right: 1px solid rgba($main-color, 0.1); + border-bottom: 1px solid rgba($main-color, 0.1); + animation: dropdown 400ms ease-in-out forwards; + box-shadow: 0px 11px 8px -3px rgba($main-color, 0.20); + } +} \ No newline at end of file diff --git a/src/OnboardingSPA/components/CheckboxTemplate/CheckboxList/index.js b/src/OnboardingSPA/components/CheckboxTemplate/CheckboxList/index.js new file mode 100644 index 000000000..51a8808e6 --- /dev/null +++ b/src/OnboardingSPA/components/CheckboxTemplate/CheckboxList/index.js @@ -0,0 +1,58 @@ +import { CheckboxItem } from '../index'; + +/** + * Checkbox List Component + * This returns a List of Checkbox Items to be placed dynamically on screen + * + * @param customItemsList.callback + * @param {Object} customItemsList - The List to be shown with a Title, Subtitle and a Description + * + * @param customItemsList.selectedItems + * @param customItemsList.customItemsList + * @return CheckboxList + */ +const CheckboxList = ( { callback, selectedItems, customItemsList } ) => { + + const length = Object.keys(customItemsList).length; + + const buildCheckboxItems = () => { + var customItems = []; + + for (const key in customItemsList) { + var item = customItemsList[key]; + const isSelectedDefault = selectedItems[item.slug]; + customItems.push( + + ); + } + + return customItems; + }; + + return ( +
+
+ { buildCheckboxItems().slice( + 0, + Math.floor( length / 2 ) + ) } +
+
+ { buildCheckboxItems().slice( + Math.floor( length / 2 ), + length + ) } +
+
+ ); +}; + +export default CheckboxList; diff --git a/src/OnboardingSPA/components/CheckboxTemplate/CheckboxList/stylesheet.scss b/src/OnboardingSPA/components/CheckboxTemplate/CheckboxList/stylesheet.scss new file mode 100644 index 000000000..564491c4a --- /dev/null +++ b/src/OnboardingSPA/components/CheckboxTemplate/CheckboxList/stylesheet.scss @@ -0,0 +1,16 @@ +.checkbox-list { + display: flex; + align-items: center; + justify-content: center; + + @media (max-width: #{ ($break-xlarge) }) { + flex-direction: column; + } + + &-col { + display: flex; + align-items: center; + flex-direction: column; + justify-content: center; + } +} \ No newline at end of file diff --git a/src/OnboardingSPA/components/CheckboxTemplate/index.js b/src/OnboardingSPA/components/CheckboxTemplate/index.js new file mode 100644 index 000000000..024215455 --- /dev/null +++ b/src/OnboardingSPA/components/CheckboxTemplate/index.js @@ -0,0 +1,2 @@ +export { default as CheckboxItem } from './CheckboxItem'; +export { default as CheckboxList } from './CheckboxList'; diff --git a/src/OnboardingSPA/data/routes/default-flow.js b/src/OnboardingSPA/data/routes/default-flow.js index c8f6bf744..e84456aa2 100644 --- a/src/OnboardingSPA/data/routes/default-flow.js +++ b/src/OnboardingSPA/data/routes/default-flow.js @@ -518,9 +518,12 @@ export const steps = [ { path: '/wp-setup/step/site-features', title: __( 'Features', 'wp-module-onboarding' ), - heading: __( 'Our toolbox is your toolbox', 'wp-module-onboarding' ), + heading: __( + 'Key features to supercharge your site', + 'wp-module-onboarding' + ), subheading: __( - "We've learned a lot in 16 years of WordPress! Now that expertise is yours.", + 'Our toolbox of Plugins & Services is your toolbox.', 'wp-module-onboarding' ), description: __( diff --git a/src/OnboardingSPA/pages/Steps/SiteFeatures/index.js b/src/OnboardingSPA/pages/Steps/SiteFeatures/index.js index 843fb52d5..6af7fc52f 100644 --- a/src/OnboardingSPA/pages/Steps/SiteFeatures/index.js +++ b/src/OnboardingSPA/pages/Steps/SiteFeatures/index.js @@ -1,21 +1,105 @@ +import { isEmpty } from 'lodash'; +import { useViewportMatch } from '@wordpress/compose'; +import { useEffect, useState } from '@wordpress/element'; +import { useSelect, useDispatch } from '@wordpress/data'; import { store as nfdOnboardingStore } from '../../../store'; -import { useDispatch } from '@wordpress/data'; -import { useEffect } from '@wordpress/element'; +import { VIEW_NAV_PRIMARY } from '../../../../constants'; import CommonLayout from '../../../components/Layouts/Common'; -import StepOverview from '../../../components/StepOverview'; +import { getSiteFeatures } from '../../../utils/api/plugins'; +import HeadingWithSubHeading from '../../../components/HeadingWithSubHeading'; +import CheckboxList from '../../../components/CheckboxTemplate/CheckboxList'; const StepSiteFeatures = () => { - const { setIsSidebarOpened, setIsHeaderNavigationEnabled } = useDispatch( nfdOnboardingStore ); + const isLargeViewport = useViewportMatch( 'medium' ); + + const [ isLoaded, setisLoaded ] = useState( false ); + const [ selectedPlugins, setSelectedPlugins ] = useState(); + const [ customPluginsList, setCustomPluginsList ] = useState(); + + const { + setIsDrawerOpened, + setDrawerActiveView, + setIsSidebarOpened, + setCurrentOnboardingData, + setIsDrawerSuppressed, + setIsHeaderNavigationEnabled, + } = useDispatch( nfdOnboardingStore ); + + const { currentStep, currentData } = useSelect( ( select ) => { + return { + currentStep: select( nfdOnboardingStore ).getCurrentStep(), + currentData: + select( nfdOnboardingStore ).getCurrentOnboardingData(), + }; + }, [] ); + + async function selectPlugin( slug, choice ) { + const selectedPluginsCopy = { ...selectedPlugins }; + selectedPluginsCopy[ slug ] = choice; + setSelectedPlugins( selectedPluginsCopy ); + + currentData.data.siteFeatures = { ...selectedPluginsCopy }; + setCurrentOnboardingData( currentData ); + } + + async function changeToStoreSchema( + customPluginsList, + saveToStore = false + ) { + const selectedPlugins = {}; + + for (const key in customPluginsList) { + var plugin = customPluginsList[key]; + selectedPlugins[plugin.slug] = plugin.selected; + } + setSelectedPlugins( selectedPlugins ); + + if ( saveToStore ) { + currentData.data.siteFeatures = { ...selectedPlugins }; + setCurrentOnboardingData( currentData ); + } + } + + async function getCustomPlugins() { + const customPluginsList = await getSiteFeatures(); + if ( isEmpty( currentData?.data?.siteFeatures ) ) + changeToStoreSchema( customPluginsList.body, true ); + else setSelectedPlugins( { ...currentData?.data?.siteFeatures } ); + + setCustomPluginsList( customPluginsList.body ); + setisLoaded( true ); + } + + useEffect( () => { + if ( ! isLoaded ) { + getCustomPlugins(); + } + }, [ isLoaded ] ); useEffect( () => { + if ( isLargeViewport ) { + setIsDrawerOpened( false ); + } setIsSidebarOpened( false ); + setIsDrawerSuppressed( false ); + setDrawerActiveView( VIEW_NAV_PRIMARY ); setIsHeaderNavigationEnabled( true ); }, [] ); return ( - - + + + { customPluginsList && ( + + ) } ); }; diff --git a/src/OnboardingSPA/static/icons/site-features/analytics.svg b/src/OnboardingSPA/static/icons/site-features/analytics.svg new file mode 100644 index 000000000..303e3c4b7 --- /dev/null +++ b/src/OnboardingSPA/static/icons/site-features/analytics.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/OnboardingSPA/static/icons/site-features/analytics_light.svg b/src/OnboardingSPA/static/icons/site-features/analytics_light.svg new file mode 100644 index 000000000..28b5002c6 --- /dev/null +++ b/src/OnboardingSPA/static/icons/site-features/analytics_light.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/OnboardingSPA/static/icons/site-features/bookingcalendar.svg b/src/OnboardingSPA/static/icons/site-features/bookingcalendar.svg new file mode 100644 index 000000000..8c3b47bf9 --- /dev/null +++ b/src/OnboardingSPA/static/icons/site-features/bookingcalendar.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/OnboardingSPA/static/icons/site-features/bookingcalendar_light.svg b/src/OnboardingSPA/static/icons/site-features/bookingcalendar_light.svg new file mode 100644 index 000000000..95cb715db --- /dev/null +++ b/src/OnboardingSPA/static/icons/site-features/bookingcalendar_light.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/OnboardingSPA/static/icons/site-features/email.svg b/src/OnboardingSPA/static/icons/site-features/email.svg new file mode 100644 index 000000000..4016aa347 --- /dev/null +++ b/src/OnboardingSPA/static/icons/site-features/email.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/OnboardingSPA/static/icons/site-features/email_light.svg b/src/OnboardingSPA/static/icons/site-features/email_light.svg new file mode 100644 index 000000000..7a7207337 --- /dev/null +++ b/src/OnboardingSPA/static/icons/site-features/email_light.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/OnboardingSPA/static/icons/site-features/filter.svg b/src/OnboardingSPA/static/icons/site-features/filter.svg new file mode 100644 index 000000000..0c91ce13f --- /dev/null +++ b/src/OnboardingSPA/static/icons/site-features/filter.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/OnboardingSPA/static/icons/site-features/filter_light.svg b/src/OnboardingSPA/static/icons/site-features/filter_light.svg new file mode 100644 index 000000000..9d6ba62ff --- /dev/null +++ b/src/OnboardingSPA/static/icons/site-features/filter_light.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/OnboardingSPA/static/icons/site-features/form.svg b/src/OnboardingSPA/static/icons/site-features/form.svg new file mode 100644 index 000000000..d0d897472 --- /dev/null +++ b/src/OnboardingSPA/static/icons/site-features/form.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/OnboardingSPA/static/icons/site-features/form_light.svg b/src/OnboardingSPA/static/icons/site-features/form_light.svg new file mode 100644 index 000000000..dc81d3598 --- /dev/null +++ b/src/OnboardingSPA/static/icons/site-features/form_light.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/OnboardingSPA/static/icons/site-features/lead.svg b/src/OnboardingSPA/static/icons/site-features/lead.svg new file mode 100644 index 000000000..ea59e7e8e --- /dev/null +++ b/src/OnboardingSPA/static/icons/site-features/lead.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/OnboardingSPA/static/icons/site-features/lead_light.svg b/src/OnboardingSPA/static/icons/site-features/lead_light.svg new file mode 100644 index 000000000..08e883fa8 --- /dev/null +++ b/src/OnboardingSPA/static/icons/site-features/lead_light.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/OnboardingSPA/static/icons/site-features/search.svg b/src/OnboardingSPA/static/icons/site-features/search.svg new file mode 100644 index 000000000..220fb5167 --- /dev/null +++ b/src/OnboardingSPA/static/icons/site-features/search.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/OnboardingSPA/static/icons/site-features/search_light.svg b/src/OnboardingSPA/static/icons/site-features/search_light.svg new file mode 100644 index 000000000..69ccf0539 --- /dev/null +++ b/src/OnboardingSPA/static/icons/site-features/search_light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/OnboardingSPA/static/icons/site-features/security.svg b/src/OnboardingSPA/static/icons/site-features/security.svg new file mode 100644 index 000000000..089ff3def --- /dev/null +++ b/src/OnboardingSPA/static/icons/site-features/security.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/OnboardingSPA/static/icons/site-features/security_light.svg b/src/OnboardingSPA/static/icons/site-features/security_light.svg new file mode 100644 index 000000000..5f028d1f4 --- /dev/null +++ b/src/OnboardingSPA/static/icons/site-features/security_light.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/OnboardingSPA/static/icons/site-features/share.svg b/src/OnboardingSPA/static/icons/site-features/share.svg new file mode 100644 index 000000000..746564332 --- /dev/null +++ b/src/OnboardingSPA/static/icons/site-features/share.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/OnboardingSPA/static/icons/site-features/share_light.svg b/src/OnboardingSPA/static/icons/site-features/share_light.svg new file mode 100644 index 000000000..65252b90a --- /dev/null +++ b/src/OnboardingSPA/static/icons/site-features/share_light.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/OnboardingSPA/static/icons/site-features/wishlist.svg b/src/OnboardingSPA/static/icons/site-features/wishlist.svg new file mode 100644 index 000000000..58d756018 --- /dev/null +++ b/src/OnboardingSPA/static/icons/site-features/wishlist.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/OnboardingSPA/static/icons/site-features/wishlist_light.svg b/src/OnboardingSPA/static/icons/site-features/wishlist_light.svg new file mode 100644 index 000000000..661edd9f7 --- /dev/null +++ b/src/OnboardingSPA/static/icons/site-features/wishlist_light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/OnboardingSPA/styles/_icons.scss b/src/OnboardingSPA/styles/_icons.scss index 2be397c74..0e02f9160 100644 --- a/src/OnboardingSPA/styles/_icons.scss +++ b/src/OnboardingSPA/styles/_icons.scss @@ -50,6 +50,27 @@ body { --nfd-onboarding-sidebar-learn-more-pages-illustration: url("../static/icons/learn-more-pages.svg"); --nfd-onboarding-sidebar-learn-more-site-features-illustration: url("../static/icons/learn-more-site-features.svg"); --nfd-onboarding-sidebar-learn-more-what-next-illustration: url("../static/icons/learn-more-what-next.svg"); + + --site-features-analytics: url("../static/icons/site-features/analytics.svg"); + --site-features-analytics--light: url("../static/icons/site-features/analytics_light.svg"); + --site-features-bookingcalendar: url("../static/icons/site-features/bookingcalendar.svg"); + --site-features-bookingcalendar--light: url("../static/icons/site-features/bookingcalendar_light.svg"); + --site-features-email: url("../static/icons/site-features/email.svg"); + --site-features-email--light: url("../static/icons/site-features/email_light.svg"); + --site-features-filter: url("../static/icons/site-features/filter.svg"); + --site-features-filter--light: url("../static/icons/site-features/filter_light.svg"); + --site-features-form: url("../static/icons/site-features/form.svg"); + --site-features-form--light: url("../static/icons/site-features/form_light.svg"); + --site-features-lead: url("../static/icons/site-features/lead.svg"); + --site-features-lead--light: url("../static/icons/site-features/lead_light.svg"); + --site-features-search: url("../static/icons/site-features/search.svg"); + --site-features-search--light: url("../static/icons/site-features/search_light.svg"); + --site-features-security: url("../static/icons/site-features/security.svg"); + --site-features-security--light: url("../static/icons/site-features/security_light.svg"); + --site-features-share: url("../static/icons/site-features/share.svg"); + --site-features-share--light: url("../static/icons/site-features/share_light.svg"); + --site-features-wishlist: url("../static/icons/site-features/wishlist.svg"); + --site-features-wishlist--light: url("../static/icons/site-features/wishlist_light.svg"); /* * Below Icons are commented because they get added to the CSS bundle and diff --git a/src/OnboardingSPA/styles/app.scss b/src/OnboardingSPA/styles/app.scss index f43d4a388..445893673 100644 --- a/src/OnboardingSPA/styles/app.scss +++ b/src/OnboardingSPA/styles/app.scss @@ -34,6 +34,8 @@ @import "../components/Button/NavCardButton/stylesheet"; @import "../pages/Steps/Ecommerce/stylesheet"; @import "../components/ErrorState/stylesheet"; +@import "../components/CheckboxTemplate/CheckboxItem/stylesheet"; +@import "../components/CheckboxTemplate/CheckboxList/stylesheet"; @import "../components/Sidebar/components/LearnMore/Skeleton/stylesheet"; // CSS for Pages diff --git a/src/OnboardingSPA/utils/api/plugins.js b/src/OnboardingSPA/utils/api/plugins.js index af7b8b8e8..7716ad275 100644 --- a/src/OnboardingSPA/utils/api/plugins.js +++ b/src/OnboardingSPA/utils/api/plugins.js @@ -30,3 +30,11 @@ export const getPluginStatus = async ( plugin ) => { } ) ); }; + +export const getSiteFeatures = async () => { + return await resolve( + apiFetch( { + url: onboardingRestURL( 'plugins/site-features' ), + } ) + ); +};