diff --git a/includes/RestApi/PluginsController.php b/includes/RestApi/PluginsController.php index a0ac35114..b2a9197f3 100644 --- a/includes/RestApi/PluginsController.php +++ b/includes/RestApi/PluginsController.php @@ -65,6 +65,19 @@ public function register_routes() { ), ) ); + + \register_rest_route( + $this->namespace, + $this->rest_base . '/status', + array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_status' ), + 'args' => $this->get_status_args(), + 'permission_callback' => array( Permissions::class, 'rest_is_authorized_admin' ), + ), + ) + ); } /** @@ -106,6 +119,19 @@ public function get_install_plugin_args() { ); } + public function get_status_args() { + return array( + 'plugin' => array( + 'type' => 'string', + 'required' => true, + ), + 'activated' => array( + 'type' => 'boolean', + 'default' => true, + ), + ); + } + /** * Verify caller has permissions to install plugins. * @@ -182,4 +208,38 @@ public function install( \WP_REST_Request $request ) { return $plugin_install_task->execute(); } + + public function get_status( \WP_REST_Request $request ) { + $plugin = $request->get_param( 'plugin' ); + $activated = $request->get_param( 'activated' ); + + if ( PluginInstaller::exists( $plugin, $activated ) ) { + return new \WP_REST_Response( + array( + 'status' => $activated ? 'activated' : 'installed', + ), + 200 + ); + } + + $position_in_queue = PluginInstallTaskManager::status( $plugin ); + + if ( $position_in_queue !== false ) { + return new \WP_REST_Response( + array( + 'status' => 'installing', + 'estimate' => ( ( $position_in_queue + 1 ) * 30 ), + ), + 200 + ); + } + + return new \WP_REST_Response( + array( + 'status' => 'inactive', + ), + 200 + ); + + } } diff --git a/includes/RestApi/SettingsController.php b/includes/RestApi/SettingsController.php index 2d4fc0a95..71da07ab5 100644 --- a/includes/RestApi/SettingsController.php +++ b/includes/RestApi/SettingsController.php @@ -5,6 +5,8 @@ use NewfoldLabs\WP\Module\Onboarding\Data\Options; use NewfoldLabs\WP\Module\Onboarding\Data\Config; use NewfoldLabs\WP\Module\Onboarding\WP_Config; +use NewfoldLabs\WP\Module\Onboarding\Data\Data; +use NewfoldLabs\WP\Module\Onboarding\Services\Webfonts; /** * Class SettingsController @@ -81,6 +83,18 @@ public function register_routes() { ), ) ); + + \register_rest_route( + $this->namespace, + $this->rest_base . '/preview', + array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_preview_settings' ), + 'permission_callback' => array( Permissions::class, 'rest_is_authorized_admin' ), + ), + ) + ); } /** @@ -177,12 +191,12 @@ public function get_current_settings() { */ public function initialize() { - if ( \get_option( Options::get_option_name( 'settings_initialized' ), false ) ) { - return new \WP_REST_Response( - array(), - 200 - ); - } + if ( \get_option( Options::get_option_name( 'settings_initialized' ), false ) ) { + return new \WP_REST_Response( + array(), + 200 + ); + } // Update wp_options $init_options = Options::get_initialization_options(); @@ -206,11 +220,25 @@ public function initialize() { $wp_config->add_constant( $constant_key, $constant_value ); } - \update_option( Options::get_option_name( 'settings_initialized' ), true ); + \update_option( Options::get_option_name( 'settings_initialized' ), true ); return new \WP_REST_Response( array(), 201 ); } + + public function get_preview_settings() { + $preview_settings = Data::preview_settings(); + + $webfonts_css = Webfonts::get_wp_theme_json_webfonts_css(); + if ( $webfonts_css !== false ) { + $preview_settings['settings']['__unstableResolvedAssets']['styles'] .= ''; + } + + return new \WP_REST_Response( + $preview_settings, + 200 + ); + } } diff --git a/includes/RestApi/Themes/ThemeInstallerController.php b/includes/RestApi/Themes/ThemeInstallerController.php index 61b23fba0..89734e3cb 100644 --- a/includes/RestApi/Themes/ThemeInstallerController.php +++ b/includes/RestApi/Themes/ThemeInstallerController.php @@ -31,8 +31,8 @@ public function register_routes() { $this->rest_base . '/initialize', array( array( - 'methods' => \WP_REST_Server::CREATABLE, - 'callback' => array( $this, 'initialize' ), + 'methods' => \WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'initialize' ), 'permission_callback' => array( Permissions::class, 'rest_is_authorized_admin' ), ), ) @@ -50,6 +50,19 @@ public function register_routes() { ), ) ); + + \register_rest_route( + $this->namespace, + $this->rest_base . '/status', + array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_status' ), + 'args' => $this->get_status_args(), + 'permission_callback' => array( Permissions::class, 'rest_is_authorized_admin' ), + ), + ) + ); } /** @@ -78,6 +91,19 @@ public function get_install_theme_args() { ); } + public function get_status_args() { + return array( + 'theme' => array( + 'type' => 'string', + 'required' => true, + ), + 'activated' => array( + 'type' => 'boolean', + 'default' => true, + ), + ); + } + /** * Queue in the initial list of themes to be installed. * @@ -139,4 +165,38 @@ public static function install( \WP_REST_Request $request ) { return $theme_install_task->execute(); } + + public function get_status( \WP_REST_Request $request ) { + $theme = $request->get_param( 'theme' ); + $activated = $request->get_param( 'activated' ); + + if ( ThemeInstaller::exists( $theme, $activated ) ) { + return new \WP_REST_Response( + array( + 'status' => $activated ? 'activated' : 'installed', + ), + 200 + ); + } + + $position_in_queue = ThemeInstallTaskManager::status( $theme ); + + if ( $position_in_queue !== false ) { + return new \WP_REST_Response( + array( + 'status' => 'installing', + 'estimate' => ( ( $position_in_queue + 1 ) * 10 ), + ), + 200 + ); + } + + return new \WP_REST_Response( + array( + 'status' => 'inactive', + ), + 200 + ); + + } } diff --git a/includes/Services/ThemeInstaller.php b/includes/Services/ThemeInstaller.php index 2b520b355..e99ac470a 100644 --- a/includes/Services/ThemeInstaller.php +++ b/includes/Services/ThemeInstaller.php @@ -119,7 +119,7 @@ public static function is_nfd_slug( $theme ) { */ public static function get_theme_stylesheet( $theme, $theme_type ) { $theme_list = Themes::get(); - return $theme_list[ $theme_type ][ $theme ]['stylesheet']; + return isset( $theme_list[ $theme_type ][ $theme ]['stylesheet'] ) ? $theme_list[ $theme_type ][ $theme ]['stylesheet'] : false; } /** diff --git a/includes/Services/Webfonts.php b/includes/Services/Webfonts.php new file mode 100644 index 000000000..f9f612a8b --- /dev/null +++ b/includes/Services/Webfonts.php @@ -0,0 +1,364 @@ +get_settings(); + + // If in the editor, add webfonts defined in variations. + if ( is_admin() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) { + $variations = \WP_Theme_JSON_Resolver::get_style_variations(); + foreach ( $variations as $variation ) { + // Skip if fontFamilies are not defined in the variation. + if ( empty( $variation['settings']['typography']['fontFamilies'] ) ) { + continue; + } + + // Initialize the array structure. + if ( empty( $settings['typography'] ) ) { + $settings['typography'] = array(); + } + if ( empty( $settings['typography']['fontFamilies'] ) ) { + $settings['typography']['fontFamilies'] = array(); + } + if ( empty( $settings['typography']['fontFamilies']['theme'] ) ) { + $settings['typography']['fontFamilies']['theme'] = array(); + } + + // Combine variations with settings. Remove duplicates. + $settings['typography']['fontFamilies']['theme'] = array_merge( $settings['typography']['fontFamilies']['theme'], $variation['settings']['typography']['fontFamilies']['theme'] ); + $settings['typography']['fontFamilies'] = array_unique( $settings['typography']['fontFamilies'] ); + } + } + + // Bail out early if there are no settings for webfonts. + if ( empty( $settings['typography']['fontFamilies'] ) ) { + return array(); + } + + $webfonts = array(); + + // Look for fontFamilies. + foreach ( $settings['typography']['fontFamilies'] as $font_families ) { + foreach ( $font_families as $font_family ) { + + // Skip if fontFace is not defined. + if ( empty( $font_family['fontFace'] ) ) { + continue; + } + + // Skip if fontFace is not an array of webfonts. + if ( ! is_array( $font_family['fontFace'] ) ) { + continue; + } + + $webfonts = array_merge( $webfonts, $font_family['fontFace'] ); + } + } + + return $webfonts; + } + + private static function transform_src_into_uri( array $src ) { + foreach ( $src as $key => $url ) { + // Tweak the URL to be relative to the theme root. + if ( ! str_starts_with( $url, 'file:./' ) ) { + continue; + } + + $src[ $key ] = get_theme_file_uri( str_replace( 'file:./', '', $url ) ); + } + + return $src; + } + + /** + * Converts the font-face properties (i.e. keys) into kebab-case. + * + * @since 6.0.0 + * + * @param array $font_face Font face to convert. + * @return array Font faces with each property in kebab-case format. + */ + private static function convert_keys_to_kebab_case( array $font_face ) { + foreach ( $font_face as $property => $value ) { + $kebab_case = _wp_to_kebab_case( $property ); + $font_face[ $kebab_case ] = $value; + if ( $kebab_case !== $property ) { + unset( $font_face[ $property ] ); + } + } + + return $font_face; + } + + /** + * Validates a webfont. + * + * @since 6.0.0 + * + * @param array $webfont The webfont arguments. + * @return array|false The validated webfont arguments, or false if the webfont is invalid. + */ + private static function validate_webfont( $webfont ) { + $webfont = wp_parse_args( + $webfont, + array( + 'font-family' => '', + 'font-style' => 'normal', + 'font-weight' => '400', + 'font-display' => 'fallback', + 'src' => array(), + ) + ); + + // Check the font-family. + if ( empty( $webfont['font-family'] ) || ! is_string( $webfont['font-family'] ) ) { + trigger_error( __( 'Webfont font family must be a non-empty string.' ) ); + + return false; + } + + // Check that the `src` property is defined and a valid type. + if ( empty( $webfont['src'] ) || ( ! is_string( $webfont['src'] ) && ! is_array( $webfont['src'] ) ) ) { + trigger_error( __( 'Webfont src must be a non-empty string or an array of strings.' ) ); + + return false; + } + + // Validate the `src` property. + foreach ( (array) $webfont['src'] as $src ) { + if ( ! is_string( $src ) || '' === trim( $src ) ) { + trigger_error( __( 'Each webfont src must be a non-empty string.' ) ); + + return false; + } + } + + // Check the font-weight. + if ( ! is_string( $webfont['font-weight'] ) && ! is_int( $webfont['font-weight'] ) ) { + trigger_error( __( 'Webfont font weight must be a properly formatted string or integer.' ) ); + + return false; + } + + // Check the font-display. + if ( ! in_array( $webfont['font-display'], array( 'auto', 'block', 'fallback', 'swap' ), true ) ) { + $webfont['font-display'] = 'fallback'; + } + + $valid_props = array( + 'ascend-override', + 'descend-override', + 'font-display', + 'font-family', + 'font-stretch', + 'font-style', + 'font-weight', + 'font-variant', + 'font-feature-settings', + 'font-variation-settings', + 'line-gap-override', + 'size-adjust', + 'src', + 'unicode-range', + ); + + foreach ( $webfont as $prop => $value ) { + if ( ! in_array( $prop, $valid_props, true ) ) { + unset( $webfont[ $prop ] ); + } + } + + return $webfont; + } + + public static function get_registered_webfonts_from_theme_json() { + $registered_webfonts = array(); + + foreach ( self::get_webfonts_from_theme_json() as $webfont ) { + if ( ! is_array( $webfont ) ) { + continue; + } + + $webfont = self::convert_keys_to_kebab_case( $webfont ); + + $webfont = self::validate_webfont( $webfont ); + + $webfont['src'] = self::transform_src_into_uri( (array) $webfont['src'] ); + + // Skip if not valid. + if ( empty( $webfont ) ) { + continue; + } + + $registered_webfonts[] = $webfont; + } + + return $registered_webfonts; + } + + private static function order_src( array $webfont ) { + $src = array(); + $src_ordered = array(); + + foreach ( $webfont['src'] as $url ) { + // Add data URIs first. + if ( str_starts_with( trim( $url ), 'data:' ) ) { + $src_ordered[] = array( + 'url' => $url, + 'format' => 'data', + ); + continue; + } + $format = pathinfo( $url, PATHINFO_EXTENSION ); + $src[ $format ] = $url; + } + + // Add woff2. + if ( ! empty( $src['woff2'] ) ) { + $src_ordered[] = array( + 'url' => sanitize_url( $src['woff2'] ), + 'format' => 'woff2', + ); + } + + // Add woff. + if ( ! empty( $src['woff'] ) ) { + $src_ordered[] = array( + 'url' => sanitize_url( $src['woff'] ), + 'format' => 'woff', + ); + } + + // Add ttf. + if ( ! empty( $src['ttf'] ) ) { + $src_ordered[] = array( + 'url' => sanitize_url( $src['ttf'] ), + 'format' => 'truetype', + ); + } + + // Add eot. + if ( ! empty( $src['eot'] ) ) { + $src_ordered[] = array( + 'url' => sanitize_url( $src['eot'] ), + 'format' => 'embedded-opentype', + ); + } + + // Add otf. + if ( ! empty( $src['otf'] ) ) { + $src_ordered[] = array( + 'url' => sanitize_url( $src['otf'] ), + 'format' => 'opentype', + ); + } + $webfont['src'] = $src_ordered; + + return $webfont; + } + + private static function compile_src( $font_family, array $value ) { + $src = "local($font_family)"; + + foreach ( $value as $item ) { + + if ( + str_starts_with( $item['url'], site_url() ) || + str_starts_with( $item['url'], home_url() ) + ) { + $item['url'] = wp_make_link_relative( $item['url'] ); + } + + $src .= ( 'data' === $item['format'] ) + ? ", url({$item['url']})" + : ", url('{$item['url']}') format('{$item['format']}')"; + } + + return $src; + } + + /** + * Compiles the font variation settings. + * + * @since 6.0.0 + * + * @param array $font_variation_settings Array of font variation settings. + * @return string The CSS. + */ + private static function compile_variations( array $font_variation_settings ) { + $variations = ''; + + foreach ( $font_variation_settings as $key => $value ) { + $variations .= "$key $value"; + } + + return $variations; + } + + private static function build_font_face_css( array $webfont ) { + $css = ''; + + // Wrap font-family in quotes if it contains spaces. + if ( + str_contains( $webfont['font-family'], ' ' ) && + ! str_contains( $webfont['font-family'], '"' ) && + ! str_contains( $webfont['font-family'], "'" ) + ) { + $webfont['font-family'] = '"' . $webfont['font-family'] . '"'; + } + + foreach ( $webfont as $key => $value ) { + /* + * Skip "provider", since it's for internal API use, + * and not a valid CSS property. + */ + if ( 'provider' === $key ) { + continue; + } + + // Compile the "src" parameter. + if ( 'src' === $key ) { + $value = self::compile_src( $webfont['font-family'], $value ); + } + + // If font-variation-settings is an array, convert it to a string. + if ( 'font-variation-settings' === $key && is_array( $value ) ) { + $value = self::compile_variations( $value ); + } + + if ( ! empty( $value ) ) { + $css .= "$key:$value;"; + } + } + + return $css; + } + + private static function get_css_from_webfonts( $registered_webfonts ) { + $css = ''; + + foreach ( $registered_webfonts as $webfont ) { + // Order the webfont's `src` items to optimize for browser support. + $webfont = self::order_src( $webfont ); + + // Build the @font-face CSS for this webfont. + $css .= '@font-face{' . self::build_font_face_css( $webfont ) . '}'; + } + + return $css; + } + + public static function get_wp_theme_json_webfonts_css() { + + $styles = self::get_css_from_webfonts( self::get_registered_webfonts_from_theme_json() ); + + if ( '' === $styles ) { + return false; + } + + return $styles; + } +} diff --git a/includes/TaskManagers/PluginInstallTaskManager.php b/includes/TaskManagers/PluginInstallTaskManager.php index 28e1f38ae..283c191b6 100644 --- a/includes/TaskManagers/PluginInstallTaskManager.php +++ b/includes/TaskManagers/PluginInstallTaskManager.php @@ -165,4 +165,9 @@ 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 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/ThemeInstallTaskManager.php b/includes/TaskManagers/ThemeInstallTaskManager.php index 1c6a0bdb1..86758ea0d 100644 --- a/includes/TaskManagers/ThemeInstallTaskManager.php +++ b/includes/TaskManagers/ThemeInstallTaskManager.php @@ -163,4 +163,9 @@ public static function add_to_queue( ThemeInstallTask $theme_install_task ) { return \update_option( Options::get_option_name( self::$queue_name ), $queue->to_array() ); } + + public static function status( $theme ) { + $themes = \get_option( Options::get_option_name( self::$queue_name ), array() ); + return array_search( $theme, array_column( $themes, 'slug' ) ); + } } diff --git a/package-lock.json b/package-lock.json index 3ceb281ff..084f23df6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3793,14 +3793,6 @@ "webpack-bundle-analyzer": "^4.4.2", "webpack-cli": "^4.9.1", "webpack-dev-server": "^4.4.0" - }, - "dependencies": { - "prettier": { - "version": "npm:wp-prettier@2.2.1-beta-1", - "resolved": "https://registry.npmjs.org/wp-prettier/-/wp-prettier-2.2.1-beta-1.tgz", - "integrity": "sha512-+JHkqs9LC/JPp51yy1hzs3lQ7qeuWCwOcSzpQNeeY/G7oSpnF61vxt7hRh87zNRTr6ob2ndy0W8rVzhgrcA+Gw==", - "dev": true - } } }, "@wordpress/style-engine": { @@ -11523,6 +11515,12 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "prettier": { + "version": "npm:wp-prettier@2.2.1-beta-1", + "resolved": "https://registry.npmjs.org/wp-prettier/-/wp-prettier-2.2.1-beta-1.tgz", + "integrity": "sha512-+JHkqs9LC/JPp51yy1hzs3lQ7qeuWCwOcSzpQNeeY/G7oSpnF61vxt7hRh87zNRTr6ob2ndy0W8rVzhgrcA+Gw==", + "dev": true + }, "prettier-linter-helpers": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", diff --git a/src/Brands/bluehost/step-error-logo.svg b/src/Brands/bluehost/step-error-logo.svg new file mode 100644 index 000000000..c75f6e6c8 --- /dev/null +++ b/src/Brands/bluehost/step-error-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Brands/hostgator/logo.svg b/src/Brands/hostgator/logo.svg index 1c80087f9..c0cae89a2 100644 --- a/src/Brands/hostgator/logo.svg +++ b/src/Brands/hostgator/logo.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/OnboardingSPA/components/Drawer/DrawerPanel/DesignThemeStylesPreview.js b/src/OnboardingSPA/components/Drawer/DrawerPanel/DesignThemeStylesPreview.js index b45518683..fe817eec7 100644 --- a/src/OnboardingSPA/components/Drawer/DrawerPanel/DesignThemeStylesPreview.js +++ b/src/OnboardingSPA/components/Drawer/DrawerPanel/DesignThemeStylesPreview.js @@ -6,6 +6,10 @@ import { store as nfdOnboardingStore } from '../../../store'; import { getPatterns } from '../../../utils/api/patterns'; import { getGlobalStyles } from '../../../utils/api/themes'; import { useGlobalStylesOutput } from '../../../utils/global-styles/use-global-styles-output'; +import { + THEME_STATUS_ACTIVE, + THEME_STATUS_NOT_ACTIVE, +} from '../../../../constants'; const DesignThemeStylesPreview = () => { const MAX_PREVIEWS_PER_ROW = 3; @@ -14,36 +18,47 @@ const DesignThemeStylesPreview = () => { const [ globalStyles, setGlobalStyles ] = useState(); const [ selectedStyle, setSelectedStyle ] = useState( '' ); - const { currentStep, currentData, storedPreviewSettings } = useSelect( - ( select ) => { + const { currentStep, currentData, storedPreviewSettings, themeStatus } = + useSelect( ( select ) => { return { currentStep: select( nfdOnboardingStore ).getCurrentStep(), currentData: select( nfdOnboardingStore ).getCurrentOnboardingData(), storedPreviewSettings: select( nfdOnboardingStore ).getPreviewSettings(), + themeStatus: select( nfdOnboardingStore ).getThemeStatus(), }; - }, - [] - ); + }, [] ); - const { updatePreviewSettings, setCurrentOnboardingData } = - useDispatch( nfdOnboardingStore ); + const { + updatePreviewSettings, + setCurrentOnboardingData, + updateThemeStatus, + } = useDispatch( nfdOnboardingStore ); const getStylesAndPatterns = async () => { - const pattern = await getPatterns( currentStep.patternId, true ); - const globalStyles = await getGlobalStyles(); - setPattern( pattern?.body ); - setGlobalStyles( globalStyles?.body ); - let selectedStyle; + const patternResponse = await getPatterns( + currentStep.patternId, + true + ); + if ( patternResponse?.error ) { + return updateThemeStatus( THEME_STATUS_NOT_ACTIVE ); + } + const globalStylesResponse = await getGlobalStyles(); + if ( globalStylesResponse?.error ) { + return updateThemeStatus( THEME_STATUS_NOT_ACTIVE ); + } + setPattern( patternResponse?.body ); + setGlobalStyles( globalStylesResponse?.body ); + let selectedGlobalStyle; if ( currentData.data.theme.variation ) { - selectedStyle = currentData.data.theme.variation; + selectedGlobalStyle = currentData.data.theme.variation; } else { - selectedStyle = globalStyles.body[ 0 ].title; - currentData.data.theme.variation = selectedStyle; + selectedGlobalStyle = globalStylesResponse.body[ 0 ].title; + currentData.data.theme.variation = selectedGlobalStyle; setCurrentOnboardingData( currentData ); } - setSelectedStyle( selectedStyle ); + setSelectedStyle( selectedGlobalStyle ); if ( document.getElementsByClassName( 'theme-styles-preview--drawer__list__item__title-bar--selected' @@ -62,8 +77,9 @@ const DesignThemeStylesPreview = () => { }; useEffect( () => { - if ( ! isLoaded ) getStylesAndPatterns(); - }, [ isLoaded ] ); + if ( ! isLoaded && themeStatus === THEME_STATUS_ACTIVE ) + getStylesAndPatterns(); + }, [ isLoaded, themeStatus ] ); const handleClick = ( idx ) => { const selectedGlobalStyle = globalStyles[ idx ]; @@ -79,6 +95,7 @@ const DesignThemeStylesPreview = () => { return globalStyles?.map( ( globalStyle, idx ) => { return ( { + return ( + + +
+

{ error }

+ +
+ ); +}; + +export default StepErrorState; diff --git a/src/OnboardingSPA/components/ErrorState/Step/stylesheet.scss b/src/OnboardingSPA/components/ErrorState/Step/stylesheet.scss new file mode 100644 index 000000000..677123a21 --- /dev/null +++ b/src/OnboardingSPA/components/ErrorState/Step/stylesheet.scss @@ -0,0 +1,25 @@ +.step-error-state { + justify-content: center; + + &__logo { + background-image: var(--nfd-onboarding-step-error-icon); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + width: 50vw; + height: 400px; + + @media (max-width: #{($break-medium)}) { + width: 100vw; + height: 30vh; + } + } + + &__error { + padding: 0; + margin-bottom: 20px; + font-weight: 400; + text-align: center; + font-size: clamp(0.85rem, 3.2vw, 1.7rem); + } +} diff --git a/src/OnboardingSPA/components/ErrorState/index.js b/src/OnboardingSPA/components/ErrorState/index.js index e69de29bb..e21af9725 100644 --- a/src/OnboardingSPA/components/ErrorState/index.js +++ b/src/OnboardingSPA/components/ErrorState/index.js @@ -0,0 +1 @@ +export { default as StepErrorState } from './Step'; diff --git a/src/OnboardingSPA/components/ErrorState/stylesheet.scss b/src/OnboardingSPA/components/ErrorState/stylesheet.scss new file mode 100644 index 000000000..21cc8520b --- /dev/null +++ b/src/OnboardingSPA/components/ErrorState/stylesheet.scss @@ -0,0 +1 @@ +@import "./Step/stylesheet"; diff --git a/src/OnboardingSPA/components/HeadingWithSubHeading/stylesheet.scss b/src/OnboardingSPA/components/HeadingWithSubHeading/stylesheet.scss index 677f827bf..daf7f201f 100644 --- a/src/OnboardingSPA/components/HeadingWithSubHeading/stylesheet.scss +++ b/src/OnboardingSPA/components/HeadingWithSubHeading/stylesheet.scss @@ -2,23 +2,23 @@ $black: var(--nfd-onboarding-black); $primary-color: var(--nfd-onboarding-primary); -.nfd-main-heading{ - width: 96%; - margin: 50px 0px 50px; +.nfd-main-heading { + width: 96%; + margin: 50px 0 50px; + line-height: 1; - &__title{ - color: $black; - font-weight: 700; - text-align: center; - color: $primary-color; - margin: 35px !important; - font-size: clamp(1.6rem, 4vw, 3.6rem); - } + &__title { + font-weight: 700; + text-align: center; + color: $primary-color; + margin: 35px !important; + font-size: clamp(1.6rem, 4vw, 3.6rem); + } - &__subtitle{ - color: $black; - font-weight: 400; - text-align: center; - font-size: clamp(0.85rem, 3.2vw, 1.7rem); - } + &__subtitle { + color: $black; + font-weight: 400; + text-align: center; + font-size: clamp(0.85rem, 3.2vw, 1.7rem); + } } diff --git a/src/OnboardingSPA/components/Loaders/Step/stylesheet.scss b/src/OnboardingSPA/components/Loaders/Step/stylesheet.scss index 252a837bc..8d61afd69 100644 --- a/src/OnboardingSPA/components/Loaders/Step/stylesheet.scss +++ b/src/OnboardingSPA/components/Loaders/Step/stylesheet.scss @@ -1,8 +1,25 @@ .step-loader { + justify-content: space-evenly; + + @media (max-width: #{($break-medium)}) { + justify-content: center; + } + &__logo { - width: 256px; + max-width: 256px; height: 256px; + width: 50vw; background-image: var(--nfd-onboarding-step-loader-icon); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + + @media (max-width: #{($break-medium)}) { + max-width: 256px; + max-height: 256px; + height: 30vh; + width: 100vw; + } } } diff --git a/src/OnboardingSPA/components/StateHandlers/Design/index.js b/src/OnboardingSPA/components/StateHandlers/Design/index.js new file mode 100644 index 000000000..df58c8074 --- /dev/null +++ b/src/OnboardingSPA/components/StateHandlers/Design/index.js @@ -0,0 +1,112 @@ +import { useSelect, useDispatch } from '@wordpress/data'; +import { Fragment, useEffect } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +import { StepLoader } from '../../Loaders'; +import { store as nfdOnboardingStore } from '../../../store'; +import { getThemeStatus } from '../../../utils/api/themes'; +import { + THEME_STATUS_INIT, + THEME_STATUS_INSTALLING, + THEME_STATUS_NOT_ACTIVE, + THEME_STATUS_ACTIVE, + DESIGN_STEPS_THEME, + THEME_INSTALL_WAIT_TIMEOUT, +} from '../../../../constants'; +import { getPreviewSettings } from '../../../utils/api/settings'; +import { StepErrorState } from '../../ErrorState'; + +const DesignStateHandler = ( { children } ) => { + const { storedThemeStatus } = useSelect( ( select ) => { + return { + storedThemeStatus: select( nfdOnboardingStore ).getThemeStatus(), + }; + }, [] ); + + const { updateThemeStatus, updatePreviewSettings } = + useDispatch( nfdOnboardingStore ); + + const checkThemeStatus = async () => { + const themeStatus = await getThemeStatus( DESIGN_STEPS_THEME ); + if ( themeStatus?.error ) { + return THEME_STATUS_NOT_ACTIVE; + } + return themeStatus.body.status; + }; + + const loadPreviewSettings = async () => { + const previewSettings = await getPreviewSettings(); + if ( previewSettings?.body ) { + updatePreviewSettings( previewSettings.body ); + } + }; + + const waitForInstall = () => { + setTimeout( async () => { + const themeStatus = await checkThemeStatus(); + if ( themeStatus !== THEME_STATUS_ACTIVE ) { + return updateThemeStatus( THEME_STATUS_NOT_ACTIVE ); + } + updateThemeStatus( themeStatus ); + await loadPreviewSettings(); + }, THEME_INSTALL_WAIT_TIMEOUT ); + }; + + useEffect( async () => { + if ( storedThemeStatus === THEME_STATUS_INIT ) { + const themeStatus = await checkThemeStatus(); + switch ( themeStatus ) { + case THEME_STATUS_INSTALLING: + waitForInstall(); + break; + case THEME_STATUS_ACTIVE: + await loadPreviewSettings(); + updateThemeStatus( themeStatus ); + break; + default: + updateThemeStatus( themeStatus ); + } + } + }, [ storedThemeStatus ] ); + + const handleRender = () => { + switch ( storedThemeStatus ) { + case THEME_STATUS_NOT_ACTIVE: + return ( + + ); + case THEME_STATUS_ACTIVE: + return children; + default: + return ( + + ); + } + }; + + return { handleRender() }; +}; + +export default DesignStateHandler; diff --git a/src/OnboardingSPA/components/StateHandlers/Ecommerce/index.js b/src/OnboardingSPA/components/StateHandlers/Ecommerce/index.js new file mode 100644 index 000000000..2dd513591 --- /dev/null +++ b/src/OnboardingSPA/components/StateHandlers/Ecommerce/index.js @@ -0,0 +1,131 @@ +import { useSelect, useDispatch } from '@wordpress/data'; +import { useEffect, useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +import { StepLoader } from '../../Loaders'; +import { store as nfdOnboardingStore } from '../../../store'; +import { getPluginStatus } from '../../../utils/api/plugins'; +import { + PLUGIN_STATUS_INIT, + PLUGIN_STATUS_INSTALLING, + PLUGIN_STATUS_NOT_ACTIVE, + PLUGIN_STATUS_ACTIVE, + ECOMMERCE_STEPS_PLUGIN, + PLUGIN_INSTALL_WAIT_TIMEOUT, +} from '../../../../constants'; +import { getPreviewSettings } from '../../../utils/api/settings'; +import { StepErrorState } from '../../ErrorState'; + +const EcommerceStateHandler = ( { children } ) => { + const [ woocommerceStatus, setWoocommerceStatus ] = useState( + PLUGIN_STATUS_INSTALLING + ); + + const { storedPluginsStatus } = useSelect( ( select ) => { + return { + storedPluginsStatus: + select( nfdOnboardingStore ).getPluginsStatus(), + }; + }, [] ); + + const { updatePluginsStatus, updatePreviewSettings } = + useDispatch( nfdOnboardingStore ); + + const checkPluginStatus = async () => { + const pluginStatus = await getPluginStatus( ECOMMERCE_STEPS_PLUGIN ); + if ( pluginStatus?.error ) { + return PLUGIN_STATUS_NOT_ACTIVE; + } + return pluginStatus.body.status; + }; + + const loadPreviewSettings = async () => { + const previewSettings = await getPreviewSettings(); + if ( previewSettings?.body ) { + updatePreviewSettings( previewSettings.body ); + } + }; + + const waitForInstall = () => { + setTimeout( async () => { + const pluginStatus = await checkPluginStatus(); + if ( pluginStatus !== PLUGIN_STATUS_ACTIVE ) { + storedPluginsStatus[ ECOMMERCE_STEPS_PLUGIN ] = + PLUGIN_STATUS_NOT_ACTIVE; + setWoocommerceStatus( PLUGIN_STATUS_NOT_ACTIVE ); + return updatePluginsStatus( storedPluginsStatus ); + } + storedPluginsStatus[ ECOMMERCE_STEPS_PLUGIN ] = pluginStatus; + setWoocommerceStatus( pluginStatus ); + updatePluginsStatus( storedPluginsStatus ); + await loadPreviewSettings(); + }, PLUGIN_INSTALL_WAIT_TIMEOUT ); + }; + + useEffect( async () => { + setWoocommerceStatus( storedPluginsStatus[ ECOMMERCE_STEPS_PLUGIN ] ); + if ( + storedPluginsStatus[ ECOMMERCE_STEPS_PLUGIN ] === PLUGIN_STATUS_INIT + ) { + const pluginStatus = await checkPluginStatus(); + switch ( pluginStatus ) { + case PLUGIN_STATUS_INSTALLING: + waitForInstall(); + break; + case PLUGIN_STATUS_ACTIVE: + await loadPreviewSettings(); + storedPluginsStatus[ ECOMMERCE_STEPS_PLUGIN ] = + pluginStatus; + setWoocommerceStatus( pluginStatus ); + updatePluginsStatus( storedPluginsStatus ); + break; + default: + storedPluginsStatus[ ECOMMERCE_STEPS_PLUGIN ] = + pluginStatus; + setWoocommerceStatus( pluginStatus ); + updatePluginsStatus( storedPluginsStatus ); + } + } + }, [ storedPluginsStatus ] ); + + const handleRender = () => { + switch ( woocommerceStatus ) { + case PLUGIN_STATUS_NOT_ACTIVE: + return ( + + ); + case PLUGIN_STATUS_ACTIVE: + return children; + default: + return ( + + ); + } + }; + + return <>{ handleRender() }; +}; + +export default EcommerceStateHandler; diff --git a/src/OnboardingSPA/components/StateHandlers/index.js b/src/OnboardingSPA/components/StateHandlers/index.js new file mode 100644 index 000000000..d91ae4080 --- /dev/null +++ b/src/OnboardingSPA/components/StateHandlers/index.js @@ -0,0 +1,2 @@ +export { default as DesignStateHandler } from './Design'; +export { default as EcommerceStateHandler } from './Ecommerce'; diff --git a/src/OnboardingSPA/pages/Steps/DesignHomepageMenu/index.js b/src/OnboardingSPA/pages/Steps/DesignHomepageMenu/index.js index d1de93405..08912e579 100644 --- a/src/OnboardingSPA/pages/Steps/DesignHomepageMenu/index.js +++ b/src/OnboardingSPA/pages/Steps/DesignHomepageMenu/index.js @@ -7,9 +7,14 @@ import { getPatterns } from '../../../utils/api/patterns'; import { getGlobalStyles } from '../../../utils/api/themes'; import { store as nfdOnboardingStore } from '../../../store'; import CommonLayout from '../../../components/Layouts/Common'; -import { VIEW_DESIGN_HOMEPAGE_MENU } from '../../../../constants'; +import { + VIEW_DESIGN_HOMEPAGE_MENU, + THEME_STATUS_ACTIVE, + THEME_STATUS_NOT_ACTIVE, +} from '../../../../constants'; import { LivePreviewSelectableCard } from '../../../components/LivePreview'; import HeadingWithSubHeading from '../../../components/HeadingWithSubHeading'; +import { DesignStateHandler } from '../../../components/StateHandlers'; import { useGlobalStylesOutput } from '../../../utils/global-styles/use-global-styles-output'; const StepDesignHomepageMenu = () => { @@ -41,8 +46,8 @@ const StepDesignHomepageMenu = () => { const isLargeViewport = useViewportMatch( 'medium' ); - const { currentStep, currentData, storedPreviewSettings } = useSelect( - ( select ) => { + const { currentStep, currentData, storedPreviewSettings, themeStatus } = + useSelect( ( select ) => { return { currentStep: select( nfdOnboardingStore ).getStepFromPath( location.pathname @@ -51,10 +56,9 @@ const StepDesignHomepageMenu = () => { select( nfdOnboardingStore ).getCurrentOnboardingData(), storedPreviewSettings: select( nfdOnboardingStore ).getPreviewSettings(), + themeStatus: select( nfdOnboardingStore ).getThemeStatus(), }; - }, - [] - ); + }, [] ); const { setDrawerActiveView, @@ -63,6 +67,7 @@ const StepDesignHomepageMenu = () => { updatePreviewSettings, setIsDrawerSuppressed, setCurrentOnboardingData, + updateThemeStatus, } = useDispatch( nfdOnboardingStore ); useEffect( () => { @@ -95,7 +100,13 @@ const StepDesignHomepageMenu = () => { async function getHomepagePatternsData() { const homepagePatternData = await getPatterns( currentStep.patternId ); + if ( homepagePatternData?.error ) { + return updateThemeStatus( THEME_STATUS_NOT_ACTIVE ); + } const globalStyles = await getGlobalStyles(); + if ( globalStyles?.error ) { + return updateThemeStatus( THEME_STATUS_NOT_ACTIVE ); + } let selectedGlobalStyle; if ( currentData.data.theme.variation ) { selectedGlobalStyle = globalStyles.body.filter( @@ -140,10 +151,10 @@ const StepDesignHomepageMenu = () => { } useEffect( () => { - if ( ! isLoaded ) { + if ( ! isLoaded && themeStatus === THEME_STATUS_ACTIVE ) { getHomepagePatternsData(); } - }, [ isLoaded ] ); + }, [ isLoaded, themeStatus ] ); function buildHomepagePreviews() { return homepagePattern?.map( ( homepage, idx ) => { @@ -167,17 +178,19 @@ const StepDesignHomepageMenu = () => { } return ( - -
- -
- { globalStyle && buildHomepagePreviews() } + + +
+ +
+ { globalStyle && buildHomepagePreviews() } +
-
- + + ); }; diff --git a/src/OnboardingSPA/pages/Steps/DesignThemeStyles/Menu/index.js b/src/OnboardingSPA/pages/Steps/DesignThemeStyles/Menu/index.js index bd508ce46..621514018 100644 --- a/src/OnboardingSPA/pages/Steps/DesignThemeStyles/Menu/index.js +++ b/src/OnboardingSPA/pages/Steps/DesignThemeStyles/Menu/index.js @@ -10,7 +10,12 @@ import HeadingWithSubHeading from '../../../../components/HeadingWithSubHeading' import { useGlobalStylesOutput } from '../../../../utils/global-styles/use-global-styles-output'; import { getPatterns } from '../../../../utils/api/patterns'; import { getGlobalStyles } from '../../../../utils/api/themes'; -import { VIEW_DESIGN_THEME_STYLES_MENU } from '../../../../../constants'; +import { + VIEW_DESIGN_THEME_STYLES_MENU, + THEME_STATUS_ACTIVE, + THEME_STATUS_NOT_ACTIVE, +} from '../../../../../constants'; +import { DesignStateHandler } from '../../../../components/StateHandlers'; const StepDesignThemeStylesMenu = () => { const MAX_PREVIEWS_PER_ROW = 3; @@ -23,19 +28,25 @@ const StepDesignThemeStylesMenu = () => { const navigate = useNavigate(); const isLargeViewport = useViewportMatch( 'medium' ); - const { currentStep, nextStep, currentData, storedPreviewSettings } = - useSelect( ( select ) => { - return { - currentStep: select( nfdOnboardingStore ).getStepFromPath( - location.pathname - ), - nextStep: select( nfdOnboardingStore ).getNextStep(), - currentData: - select( nfdOnboardingStore ).getCurrentOnboardingData(), - storedPreviewSettings: - select( nfdOnboardingStore ).getPreviewSettings(), - }; - }, [] ); + const { + currentStep, + nextStep, + currentData, + storedPreviewSettings, + themeStatus, + } = useSelect( ( select ) => { + return { + currentStep: select( nfdOnboardingStore ).getStepFromPath( + location.pathname + ), + nextStep: select( nfdOnboardingStore ).getNextStep(), + currentData: + select( nfdOnboardingStore ).getCurrentOnboardingData(), + storedPreviewSettings: + select( nfdOnboardingStore ).getPreviewSettings(), + themeStatus: select( nfdOnboardingStore ).getThemeStatus(), + }; + }, [] ); const { setDrawerActiveView, @@ -44,6 +55,7 @@ const StepDesignThemeStylesMenu = () => { setIsDrawerSuppressed, updatePreviewSettings, setCurrentOnboardingData, + updateThemeStatus, } = useDispatch( nfdOnboardingStore ); useEffect( () => { @@ -56,17 +68,27 @@ const StepDesignThemeStylesMenu = () => { }, [] ); const getStylesAndPatterns = async () => { - const pattern = await getPatterns( currentStep.patternId, true ); - const globalStyles = await getGlobalStyles(); - setPattern( pattern?.body ); - setGlobalStyles( globalStyles?.body ); + const patternsResponse = await getPatterns( + currentStep.patternId, + true + ); + if ( patternsResponse?.error ) { + return updateThemeStatus( THEME_STATUS_NOT_ACTIVE ); + } + const globalStylesResponse = await getGlobalStyles(); + if ( globalStylesResponse?.error ) { + return updateThemeStatus( THEME_STATUS_NOT_ACTIVE ); + } + setPattern( patternsResponse?.body ); + setGlobalStyles( globalStylesResponse?.body ); setSelectedStyle( currentData.data.theme.variation ); setIsLoaded( true ); }; useEffect( () => { - if ( ! isLoaded ) getStylesAndPatterns(); - }, [ isLoaded ] ); + if ( ! isLoaded && themeStatus === THEME_STATUS_ACTIVE ) + getStylesAndPatterns(); + }, [ isLoaded, themeStatus ] ); const handleClick = ( idx ) => { const selectedGlobalStyle = globalStyles[ idx ]; @@ -83,6 +105,7 @@ const StepDesignThemeStylesMenu = () => { return globalStyles?.map( ( globalStyle, idx ) => { return ( { }; return ( - -
- -
- { globalStyles && - buildPreviews().slice( 0, MAX_PREVIEWS_PER_ROW ) } -
-
- { globalStyles && - buildPreviews().slice( - MAX_PREVIEWS_PER_ROW, - globalStyles.length - ) } + + +
+ +
+ { globalStyles && + buildPreviews().slice( 0, MAX_PREVIEWS_PER_ROW ) } +
+
+ { globalStyles && + buildPreviews().slice( + MAX_PREVIEWS_PER_ROW, + globalStyles.length + ) } +
-
- + + ); }; diff --git a/src/OnboardingSPA/pages/Steps/DesignThemeStyles/Preview/index.js b/src/OnboardingSPA/pages/Steps/DesignThemeStyles/Preview/index.js index 29b418b16..6b44e9889 100644 --- a/src/OnboardingSPA/pages/Steps/DesignThemeStyles/Preview/index.js +++ b/src/OnboardingSPA/pages/Steps/DesignThemeStyles/Preview/index.js @@ -8,12 +8,17 @@ import { orderBy, filter } from 'lodash'; import { LivePreview } from '../../../../components/LivePreview'; import CommonLayout from '../../../../components/Layouts/Common'; -import { VIEW_DESIGN_THEME_STYLES_PREVIEW } from '../../../../../constants'; +import { + VIEW_DESIGN_THEME_STYLES_PREVIEW, + THEME_STATUS_ACTIVE, + THEME_STATUS_NOT_ACTIVE, +} from '../../../../../constants'; import { store as nfdOnboardingStore } from '../../../../store'; import { getPatterns } from '../../../../utils/api/patterns'; import { getGlobalStyles } from '../../../../utils/api/themes'; import { useGlobalStylesOutput } from '../../../../utils/global-styles/use-global-styles-output'; import { conditionalSteps } from '../../../../data/routes/'; +import { DesignStateHandler } from '../../../../components/StateHandlers'; const StepDesignThemeStylesPreview = () => { const location = useLocation(); @@ -29,6 +34,7 @@ const StepDesignThemeStylesPreview = () => { routes, designSteps, allSteps, + themeStatus, } = useSelect( ( select ) => { return { currentStep: select( nfdOnboardingStore ).getStepFromPath( @@ -41,6 +47,7 @@ const StepDesignThemeStylesPreview = () => { routes: select( nfdOnboardingStore ).getRoutes(), allSteps: select( nfdOnboardingStore ).getAllSteps(), designSteps: select( nfdOnboardingStore ).getDesignSteps(), + themeStatus: select( nfdOnboardingStore ).getThemeStatus(), }; }, [] ); @@ -54,6 +61,7 @@ const StepDesignThemeStylesPreview = () => { updateDesignSteps, updateAllSteps, setCurrentOnboardingData, + updateThemeStatus, } = useDispatch( nfdOnboardingStore ); useEffect( () => { @@ -67,42 +75,52 @@ const StepDesignThemeStylesPreview = () => { }, [] ); const getStylesAndPatterns = async () => { - const pattern = await getPatterns( currentStep.patternId, true ); - const globalStyles = await getGlobalStyles(); + const patternsResponse = await getPatterns( + currentStep.patternId, + true + ); + if ( patternsResponse?.error ) { + return updateThemeStatus( THEME_STATUS_NOT_ACTIVE ); + } + const globalStylesResponse = await getGlobalStyles(); + if ( globalStylesResponse?.error ) { + return updateThemeStatus( THEME_STATUS_NOT_ACTIVE ); + } let selectedGlobalStyle; if ( currentData.data.theme.variation ) { - selectedGlobalStyle = globalStyles.body.filter( + selectedGlobalStyle = globalStylesResponse.body.filter( ( globalStyle ) => globalStyle.title === currentData.data.theme.variation )[ 0 ]; } else { - selectedGlobalStyle = globalStyles.body[ 0 ]; + selectedGlobalStyle = globalStylesResponse.body[ 0 ]; } updatePreviewSettings( useGlobalStylesOutput( selectedGlobalStyle, storedPreviewSettings ) ); - setPattern( pattern?.body ); + setPattern( patternsResponse?.body ); setIsLoaded( true ); }; const addColorAndTypographyRoutes = () => { + const updates = removeColorAndTypographyRoutes(); const steps = [ conditionalSteps.designColors, conditionalSteps.designTypography, ]; return { routes: orderBy( - routes.concat( steps ), + updates.routes.concat( steps ), [ 'priority' ], [ 'asc' ] ), allSteps: orderBy( - allSteps.concat( steps ), + updates.allSteps.concat( steps ), [ 'priority' ], [ 'asc' ] ), designSteps: orderBy( - designSteps.concat( steps ), + updates.designSteps.concat( steps ), [ 'priority' ], [ 'asc' ] ), @@ -165,50 +183,53 @@ const StepDesignThemeStylesPreview = () => { }; useEffect( () => { - if ( ! isLoaded ) getStylesAndPatterns(); - }, [ isLoaded ] ); + if ( ! isLoaded && themeStatus === THEME_STATUS_ACTIVE ) + getStylesAndPatterns(); + }, [ isLoaded, themeStatus ] ); return ( - -
- - - { __( - 'Customize Colors & Fonts?', - 'wp-module-onboarding' - ) } - + + +
+ + { __( - 'Check to customize in the next few steps (or leave empty and use the Site Editor later)', + 'Customize Colors & Fonts?', 'wp-module-onboarding' ) } + + { __( + 'Check to customize in the next few steps (or leave empty and use the Site Editor later)', + 'wp-module-onboarding' + ) } + - -
- } - checked={ customize } - onChange={ () => handleCheckbox( ! customize ) } - /> -
-
-
- - - -
-
-
- { pattern && ( - + } + checked={ customize } + onChange={ () => handleCheckbox( ! customize ) } /> - ) } -
-
+
+
+
+ + + +
+
+
+ { pattern && ( + + ) } +
+
+ ); }; diff --git a/src/OnboardingSPA/pages/Steps/Ecommerce/StepAddress/index.js b/src/OnboardingSPA/pages/Steps/Ecommerce/StepAddress/index.js index 4028a2e0b..d5fa5e624 100644 --- a/src/OnboardingSPA/pages/Steps/Ecommerce/StepAddress/index.js +++ b/src/OnboardingSPA/pages/Steps/Ecommerce/StepAddress/index.js @@ -12,6 +12,7 @@ import { store as nfdOnboardingStore } from '../../../../store'; import content from '../content.json'; import countries from '../countries.json'; import { useWPSettings } from '../useWPSettings'; +import { EcommerceStateHandler } from '../../../../components/StateHandlers'; const StepAddress = () => { const isLargeViewport = useViewportMatch( 'medium' ); @@ -107,6 +108,7 @@ const StepAddress = () => { }); } return ( +
@@ -243,6 +245,7 @@ const StepAddress = () => {
+
); }; diff --git a/src/OnboardingSPA/pages/Steps/Ecommerce/StepProducts/index.js b/src/OnboardingSPA/pages/Steps/Ecommerce/StepProducts/index.js index e8ec55970..c9d15bf4e 100644 --- a/src/OnboardingSPA/pages/Steps/Ecommerce/StepProducts/index.js +++ b/src/OnboardingSPA/pages/Steps/Ecommerce/StepProducts/index.js @@ -9,6 +9,7 @@ import CardHeader from '../../../../components/CardHeader'; import CommonLayout from '../../../../components/Layouts/Common'; import NeedHelpTag from '../../../../components/NeedHelpTag'; import NewfoldLargeCard from '../../../../components/NewfoldLargeCard'; +import { EcommerceStateHandler } from '../../../../components/StateHandlers'; import { store as nfdOnboardingStore } from '../../../../store'; import content from '../content.json'; @@ -58,6 +59,7 @@ const StepProducts = () => { return ( +
@@ -100,6 +102,7 @@ const StepProducts = () => {
+
); }; diff --git a/src/OnboardingSPA/pages/Steps/Ecommerce/StepTax/index.js b/src/OnboardingSPA/pages/Steps/Ecommerce/StepTax/index.js index 5ddeb6f26..90f6133ae 100644 --- a/src/OnboardingSPA/pages/Steps/Ecommerce/StepTax/index.js +++ b/src/OnboardingSPA/pages/Steps/Ecommerce/StepTax/index.js @@ -9,6 +9,7 @@ import CardHeader from '../../../../components/CardHeader'; import CommonLayout from '../../../../components/Layouts/Common'; import NeedHelpTag from '../../../../components/NeedHelpTag'; import NewfoldLargeCard from '../../../../components/NewfoldLargeCard'; +import { EcommerceStateHandler } from '../../../../components/StateHandlers'; import { store as nfdOnboardingStore } from '../../../../store'; import content from '../content.json'; import { useWPSettings } from '../useWPSettings'; @@ -74,6 +75,7 @@ const StepTax = () => { }; return ( +
@@ -121,6 +123,7 @@ const StepTax = () => {
+
); }; diff --git a/src/OnboardingSPA/static/images/content.png b/src/OnboardingSPA/static/images/content.png index bcf0a78a3..28abb6a00 100644 Binary files a/src/OnboardingSPA/static/images/content.png and b/src/OnboardingSPA/static/images/content.png differ diff --git a/src/OnboardingSPA/static/images/design.png b/src/OnboardingSPA/static/images/design.png index e6affc8e8..d65d00d28 100644 Binary files a/src/OnboardingSPA/static/images/design.png and b/src/OnboardingSPA/static/images/design.png differ diff --git a/src/OnboardingSPA/static/images/features.png b/src/OnboardingSPA/static/images/features.png index 497aa4bf0..95fece879 100644 Binary files a/src/OnboardingSPA/static/images/features.png and b/src/OnboardingSPA/static/images/features.png differ diff --git a/src/OnboardingSPA/store/actions.js b/src/OnboardingSPA/store/actions.js index b6bfdd990..9e6258a6b 100644 --- a/src/OnboardingSPA/store/actions.js +++ b/src/OnboardingSPA/store/actions.js @@ -118,6 +118,20 @@ export function updateSettings( settings ) { }; } +export function updateThemeStatus( themeStatus ) { + return { + type: 'UPDATE_THEME_STATUS', + themeStatus, + }; +} + +export function updatePluginsStatus( pluginsStatus ) { + return { + type: 'UPDATE_PLUGINS_STATUS', + pluginsStatus, + }; +} + export function setIsSidebarOpened( isOpen ) { return { type: 'SET_SIDEBAR_OPENED', diff --git a/src/OnboardingSPA/store/reducer.js b/src/OnboardingSPA/store/reducer.js index 3040619a5..128e35f35 100644 --- a/src/OnboardingSPA/store/reducer.js +++ b/src/OnboardingSPA/store/reducer.js @@ -1,6 +1,11 @@ import { combineReducers } from '@wordpress/data'; -import { VIEW_NAV_PRIMARY } from '../../constants'; +import { + VIEW_NAV_PRIMARY, + THEME_STATUS_INIT, + PLUGIN_STATUS_INIT, + ECOMMERCE_STEPS_PLUGIN, +} from '../../constants'; import { routes as initialRoutes, @@ -148,13 +153,29 @@ export function runtime( state = {}, action ) { return state; } -export function settings( state = {}, action ) { +export function settings( + state = { + themeStatus: THEME_STATUS_INIT, + pluginsStatus: { [ ECOMMERCE_STEPS_PLUGIN ]: PLUGIN_STATUS_INIT }, + }, + action +) { switch ( action.type ) { case 'UPDATE_SETTINGS': return { ...state, ...action.settings, }; + case 'UPDATE_THEME_STATUS': + return { + ...state, + themeStatus: action.themeStatus, + }; + case 'UPDATE_PLUGINS_STATUS': + return { + ...state, + pluginsStatus: action.pluginsStatus, + }; } return state; diff --git a/src/OnboardingSPA/store/selectors.js b/src/OnboardingSPA/store/selectors.js index d1245ae6e..a83d985ac 100644 --- a/src/OnboardingSPA/store/selectors.js +++ b/src/OnboardingSPA/store/selectors.js @@ -229,6 +229,18 @@ export function getPreviewSettings( state ) { return state.runtime.previewSettings; } +export function getSettings ( state ) { + return state.settings; +} + +export function getThemeStatus ( state ) { + return state.settings.themeStatus; +} + +export function getPluginsStatus ( state ) { + return state.settings.pluginsStatus; +} + export function getStoreInfoSteps( state ) { return state.flow.steps.storeInfoSteps; } diff --git a/src/OnboardingSPA/styles/_branding.scss b/src/OnboardingSPA/styles/_branding.scss index 27196c84c..a3d527234 100644 --- a/src/OnboardingSPA/styles/_branding.scss +++ b/src/OnboardingSPA/styles/_branding.scss @@ -1,4 +1,5 @@ body { + &.nfd-brand-bluehost { --wp-admin-theme-color: #3575d3; --wp-admin-theme-color--rgb: 53, 117, 211; @@ -25,8 +26,10 @@ body { --nfd-onboarding-border: 219, 219, 219; --nfd-onboarding-drawer-icon-fill: var(--nfd-onboarding-tertiary); --nfd-onboarding-drawer-icon-active-fill: var(--nfd-onboarding-light); - --nfd-onboarding-step-loader-icon: url("../../Brands/bluehost/step-loader-logo.svg"); + --nfd-onboarding-step-loader-icon: url("../../Brands/bluehost/step-loader-logo.svg"); + --nfd-onboarding-step-error-icon: url("../../Brands/bluehost/step-error-logo.svg"); } + &.nfd-brand-hostgator { --wp-admin-theme-color: #2e93ee; --wp-admin-theme-color--rgb: 46, 147, 238; @@ -54,6 +57,7 @@ body { --nfd-onboarding-drawer-icon-fill: var(--nfd-onboarding-tertiary); --nfd-onboarding-drawer-icon-active-fill: var(--nfd-onboarding-light); } + &.nfd-brand-webcom { --wp-admin-theme-color: #4b75fc; --wp-admin-theme-color--rgb: 75, 117, 252; @@ -79,8 +83,6 @@ body { --nfd-onboarding-dark: #000000; --nfd-onboarding-border: 219, 219, 219; --nfd-onboarding-drawer-icon-fill: var(--nfd-onboarding-tertiary-alt); - --nfd-onboarding-drawer-icon-active-fill: var( - --nfd-onboarding-tertiary-alt - ); + --nfd-onboarding-drawer-icon-active-fill: var(--nfd-onboarding-tertiary-alt); } } diff --git a/src/OnboardingSPA/styles/_icons.scss b/src/OnboardingSPA/styles/_icons.scss index 7d3424d40..87d3824d0 100644 --- a/src/OnboardingSPA/styles/_icons.scss +++ b/src/OnboardingSPA/styles/_icons.scss @@ -33,6 +33,7 @@ body { --business-icon: url("../static/icons/business.svg"); --business-white-icon: url("../static/icons/business-white.svg"); + --nfd-onboarding-step-error-icon: url("../../Brands/bluehost/step-error-logo.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 9c8c4f5f4..90d5a770d 100644 --- a/src/OnboardingSPA/styles/app.scss +++ b/src/OnboardingSPA/styles/app.scss @@ -33,6 +33,7 @@ @import "../pages/Steps/GetStarted/GetStartedExperience/stylesheet"; @import "../components/Button/NavCardButton/stylesheet"; @import "../pages/Steps/Ecommerce/stylesheet"; +@import "../components/ErrorState/stylesheet"; // CSS for Pages @import "../pages/Steps/BasicInfo/stylesheet.scss"; diff --git a/src/OnboardingSPA/utils/api/plugins.js b/src/OnboardingSPA/utils/api/plugins.js index 398d01125..af7b8b8e8 100644 --- a/src/OnboardingSPA/utils/api/plugins.js +++ b/src/OnboardingSPA/utils/api/plugins.js @@ -2,6 +2,7 @@ import apiFetch from '@wordpress/api-fetch'; import { onboardingRestURL } from './common'; import { getQueryParam } from '../index'; +import { resolve } from './resolve'; import { NFD_PLUGINS_QUERY_PARAM } from '../../../constants'; export const init = () => { @@ -19,3 +20,13 @@ export const init = () => { console.error( error ); } ); }; + +export const getPluginStatus = async ( plugin ) => { + return await resolve( + apiFetch( { + url: onboardingRestURL( + 'plugins/status' + ( plugin ? `&plugin=${ plugin }` : '' ) + ), + } ) + ); +}; diff --git a/src/OnboardingSPA/utils/api/settings.js b/src/OnboardingSPA/utils/api/settings.js index 9c571f752..831f7e4cf 100644 --- a/src/OnboardingSPA/utils/api/settings.js +++ b/src/OnboardingSPA/utils/api/settings.js @@ -32,3 +32,11 @@ export const initialize = ( retries = 0 ) => { initialize( retries ); } ); }; + +export const getPreviewSettings = async () => { + return await resolve( + apiFetch( { + url: onboardingRestURL( 'settings/preview' ), + } ) + ); +} diff --git a/src/OnboardingSPA/utils/api/themes.js b/src/OnboardingSPA/utils/api/themes.js index 4b88bb46a..4f21d60c1 100644 --- a/src/OnboardingSPA/utils/api/themes.js +++ b/src/OnboardingSPA/utils/api/themes.js @@ -24,4 +24,14 @@ const getGlobalStyles = async () => { ); }; -export { init, getGlobalStyles }; +const getThemeStatus = async ( theme ) => { + return await resolve( + apiFetch( { + url: onboardingRestURL( + 'themes/status' + ( theme ? `&theme=${ theme }` : '' ) + ), + } ) + ); +}; + +export { init, getGlobalStyles, getThemeStatus }; diff --git a/src/constants.js b/src/constants.js index 99ee4b99e..89a3c7878 100644 --- a/src/constants.js +++ b/src/constants.js @@ -30,6 +30,21 @@ export const MAX_RETRIES_SETTINGS_INIT = 2; export const NFD_PLUGINS_QUERY_PARAM = 'nfd_plugins'; export const NFD_THEMES_QUERY_PARAM = 'nfd_themes'; +// [TODO] Read the theme from flow data once we have the themes step. +export const DESIGN_STEPS_THEME = 'nfd_slug_yith_wonder' +export const THEME_STATUS_INIT = 'init'; +export const THEME_STATUS_NOT_ACTIVE = 'inactive'; +export const THEME_STATUS_INSTALLING = 'installing'; +export const THEME_STATUS_ACTIVE = 'activated'; +export const THEME_INSTALL_WAIT_TIMEOUT = 30000 + +export const ECOMMERCE_STEPS_PLUGIN = 'woocommerce' +export const PLUGIN_STATUS_INIT = 'init'; +export const PLUGIN_STATUS_NOT_ACTIVE = 'inactive'; +export const PLUGIN_STATUS_INSTALLING = 'installing'; +export const PLUGIN_STATUS_ACTIVE = 'activated'; +export const PLUGIN_INSTALL_WAIT_TIMEOUT = 30000 + /** * All views for the component. */