From aa3e24edfadc794ef447ff3b7430f3179abb8970 Mon Sep 17 00:00:00 2001 From: Hardeep Asrani Date: Mon, 20 Nov 2023 16:23:35 +0530 Subject: [PATCH 01/37] Initial work on Onboarding --- inc/class-main.php | 1 + inc/plugins/class-fse-onboarding.php | 125 ++++ src/onboarding/components/App.js | 557 ++++++++++++++++++ src/onboarding/components/PalettePreview.js | 133 +++++ src/onboarding/components/Sidebar.js | 119 ++++ src/onboarding/components/steps/Appearance.js | 50 ++ src/onboarding/components/steps/SiteInfo.js | 97 +++ src/onboarding/index.js | 27 + src/onboarding/store.js | 83 +++ src/onboarding/style.scss | 239 ++++++++ src/onboarding/utils.js | 18 + webpack.config.js | 24 +- 12 files changed, 1472 insertions(+), 1 deletion(-) create mode 100644 inc/plugins/class-fse-onboarding.php create mode 100644 src/onboarding/components/App.js create mode 100644 src/onboarding/components/PalettePreview.js create mode 100644 src/onboarding/components/Sidebar.js create mode 100644 src/onboarding/components/steps/Appearance.js create mode 100644 src/onboarding/components/steps/SiteInfo.js create mode 100644 src/onboarding/index.js create mode 100644 src/onboarding/store.js create mode 100644 src/onboarding/style.scss create mode 100644 src/onboarding/utils.js diff --git a/inc/class-main.php b/inc/class-main.php index b42129a89..fe0ba4de3 100644 --- a/inc/class-main.php +++ b/inc/class-main.php @@ -67,6 +67,7 @@ public function autoload_classes() { '\ThemeIsle\GutenbergBlocks\Plugins\Block_Conditions', '\ThemeIsle\GutenbergBlocks\Plugins\Dashboard', '\ThemeIsle\GutenbergBlocks\Plugins\Dynamic_Content', + '\ThemeIsle\GutenbergBlocks\Plugins\FSE_Onboarding', '\ThemeIsle\GutenbergBlocks\Plugins\Options_Settings', '\ThemeIsle\GutenbergBlocks\Plugins\Stripe_API', '\ThemeIsle\GutenbergBlocks\Render\Masonry_Variant', diff --git a/inc/plugins/class-fse-onboarding.php b/inc/plugins/class-fse-onboarding.php new file mode 100644 index 000000000..ac2f78af2 --- /dev/null +++ b/inc/plugins/class-fse-onboarding.php @@ -0,0 +1,125 @@ +get_data(); + + $default_variation = array( + 'title' => __( 'Default', 'otter-blocks' ), + 'settings' => $data, + ); + + wp_localize_script( + 'otter-onboarding-scripts', + 'otterObj', + apply_filters( + 'otter_onboarding_data', + array( + 'version' => OTTER_BLOCKS_VERSION, + 'assetsPath' => OTTER_BLOCKS_URL . 'assets/', + 'themeStyles' => $variations, + 'defaultStyles' => $default_variation, + ) + ) + ); + } + + /** + * The instance method for the static class. + * Defines and returns the instance of the static class. + * + * @static + * @since 1.7.1 + * @access public + * @return FSE_Onboarding + */ + public static function instance() { + if ( is_null( self::$instance ) ) { + self::$instance = new self(); + self::$instance->init(); + } + + return self::$instance; + } + + /** + * Throw error on object clone + * + * The whole idea of the singleton design pattern is that there is a single + * object therefore, we don't want the object to be cloned. + * + * @access public + * @since 1.7.1 + * @return void + */ + public function __clone() { + // Cloning instances of the class is forbidden. + _doing_it_wrong( __FUNCTION__, esc_html__( 'Cheatin’ huh?', 'otter-blocks' ), '1.0.0' ); + } + + /** + * Disable unserializing of the class + * + * @access public + * @since 1.7.1 + * @return void + */ + public function __wakeup() { + // Unserializing instances of the class is forbidden. + _doing_it_wrong( __FUNCTION__, esc_html__( 'Cheatin’ huh?', 'otter-blocks' ), '1.0.0' ); + } +} diff --git a/src/onboarding/components/App.js b/src/onboarding/components/App.js new file mode 100644 index 000000000..72ae921e9 --- /dev/null +++ b/src/onboarding/components/App.js @@ -0,0 +1,557 @@ +/** + * WordPress dependencies. + */ +import { __ } from '@wordpress/i18n'; + +import { BlockPreview } from '@wordpress/block-editor'; + +/** + * Internal dependencies. + */ +import Sidebar from './Sidebar'; + +const blocks = [ + { + 'clientId': '7de83d98-f17a-4f5f-8873-da7b229d990f', + 'name': 'core/template-part', + 'isValid': true, + 'originalContent': '', + 'validationIssues': [], + 'attributes': { + 'slug': 'header', + 'theme': 'raft', + 'tagName': 'header', + 'hasCustomCSS': false, + 'otterConditions': [] + }, + 'innerBlocks': [] + }, + { + 'clientId': '032637d0-12ee-470b-b5cc-84e46f1dce66', + 'name': 'core/group', + 'isValid': true, + 'originalContent': '
\n\n\n\n\n\n\n\n\n\n\n\n
', + 'validationIssues': [], + 'attributes': { + 'tagName': 'div', + 'backgroundColor': 'raft-bg-alt', + 'style': { + 'spacing': { + 'padding': { + 'top': '64px', + 'bottom': '64px' + }, + 'blockGap': '24px', + 'margin': { + 'top': '0px', + 'bottom': '0px' + } + } + }, + 'layout': { + 'inherit': true, + 'type': 'constrained' + }, + 'otterConditions': [], + 'hasCustomCSS': false + }, + 'innerBlocks': [ + { + 'clientId': '36ab4e10-5eec-4633-a144-b09208a04fae', + 'name': 'core/heading', + 'isValid': true, + 'originalContent': '

Block-based websites made simple.

', + 'validationIssues': [], + 'attributes': { + 'textAlign': 'center', + 'content': 'Block-based websites made simple.', + 'level': 1, + 'fontSize': 'huge', + 'otterConditions': [], + 'hasCustomCSS': false + }, + 'innerBlocks': [] + }, + { + 'clientId': '8bc5b509-0cf3-4b6e-bada-79cbfe1a6542', + 'name': 'core/paragraph', + 'isValid': true, + 'originalContent': '

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor ut labore et dolore magna aliqua.

', + 'validationIssues': [], + 'attributes': { + 'align': 'center', + 'content': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor ut labore et dolore magna aliqua.', + 'dropCap': false, + 'fontSize': 'medium', + 'hasCustomCSS': false, + 'otterConditions': [] + }, + 'innerBlocks': [] + }, + { + 'clientId': '21111f4a-9696-43de-8243-8b49ca8a068f', + 'name': 'core/buttons', + 'isValid': true, + 'originalContent': '
\n\n\n\n
', + 'validationIssues': [], + 'attributes': { + 'layout': { + 'type': 'flex', + 'justifyContent': 'center' + }, + 'hasCustomCSS': false, + 'otterConditions': [] + }, + 'innerBlocks': [ + { + 'clientId': '642d719f-b419-4b2d-8a61-9624cf92b595', + 'name': 'core/button', + 'isValid': true, + 'originalContent': '
\nLearn More\n
', + 'validationIssues': [], + 'attributes': { + 'text': 'Learn More', + 'textColor': 'raft-fg-alt', + 'otterConditions': [], + 'hasCustomCSS': false + }, + 'innerBlocks': [] + } + ] + }, + { + 'clientId': '214e3be8-fd03-4d9d-8747-da041dc79ef3', + 'name': 'core/spacer', + 'isValid': true, + 'originalContent': '', + 'validationIssues': [], + 'attributes': { + 'height': '3vw', + 'hasCustomCSS': false, + 'otterConditions': [] + }, + 'innerBlocks': [] + }, + { + 'clientId': '757d828c-1fa0-49ae-af42-87a2833c6368', + 'name': 'core/image', + 'isValid': true, + 'originalContent': '
\nHero Illustration\n
', + 'validationIssues': [], + 'attributes': { + 'align': 'center', + 'url': 'http://localhost:8888/wp-content/themes/raft/assets/img/raft-illustration.svg', + 'alt': 'Hero Illustration', + 'caption': '', + 'sizeSlug': 'large', + 'linkDestination': 'none', + 'hasCustomCSS': false, + 'otterConditions': [], + 'boxShadow': false, + 'boxShadowColor': '#000000', + 'boxShadowColorOpacity': 50, + 'boxShadowBlur': 5, + 'boxShadowHorizontal': 0, + 'boxShadowVertical': 0 + }, + 'innerBlocks': [] + } + ] + }, + { + 'clientId': '0dc9b2ee-ecea-4893-ac43-9ec4de5b981f', + 'name': 'core/group', + 'isValid': true, + 'originalContent': '
\n \n
', + 'validationIssues': [], + 'attributes': { + 'tagName': 'div', + 'style': { + 'spacing': { + 'padding': { + 'top': '64px', + 'bottom': '64px' + }, + 'margin': { + 'top': '0px', + 'bottom': '0px' + } + } + }, + 'layout': { + 'type': 'constrained' + }, + 'hasCustomCSS': false, + 'otterConditions': [] + }, + 'innerBlocks': [ + { + 'clientId': '002c8b37-2ece-4151-b151-6b114f00b181', + 'name': 'core/post-content', + 'isValid': true, + 'originalContent': '', + 'validationIssues': [], + 'attributes': { + 'layout': { + 'type': 'default' + }, + 'hasCustomCSS': false, + 'otterConditions': [] + }, + 'innerBlocks': [] + } + ] + }, + { + 'clientId': '7046e62b-9094-4c1e-87ac-cdc1c874cdc7', + 'name': 'core/group', + 'isValid': true, + 'originalContent': '
\n\n\n\n
', + 'validationIssues': [], + 'attributes': { + 'tagName': 'div', + 'align': 'full', + 'style': { + 'spacing': { + 'padding': { + 'top': '64px', + 'bottom': '64px' + }, + 'margin': { + 'top': '0px', + 'bottom': '0px' + } + } + }, + 'layout': { + 'type': 'constrained' + }, + 'hasCustomCSS': false, + 'otterConditions': [] + }, + 'innerBlocks': [ + { + 'clientId': '520f4d8e-1237-4920-aa5c-92718b033bd3', + 'name': 'core/columns', + 'isValid': true, + 'originalContent': '
', + 'validationIssues': [], + 'attributes': { + 'isStackedOnMobile': true, + 'align': 'wide', + 'hasCustomCSS': false, + 'otterConditions': [] + }, + 'innerBlocks': [ + { + 'clientId': '95527c39-32d2-4e9a-8a16-53e4c3e2b893', + 'name': 'core/column', + 'isValid': true, + 'originalContent': '
', + 'validationIssues': [], + 'attributes': { + 'hasCustomCSS': false, + 'otterConditions': [] + }, + 'innerBlocks': [ + { + 'clientId': 'c12b5e3f-6513-4a23-be28-055e2fc7da9c', + 'name': 'core/image', + 'isValid': true, + 'originalContent': '
Illustration
', + 'validationIssues': [], + 'attributes': { + 'url': 'http://localhost:8888/wp-content/themes/raft/assets/img/shape-05.svg', + 'alt': 'Illustration', + 'caption': [], + 'linkDestination': 'none', + 'className': 'size-full', + 'otterConditions': [], + 'boxShadow': false, + 'boxShadowColor': '#000000', + 'boxShadowColorOpacity': 50, + 'boxShadowBlur': 5, + 'boxShadowHorizontal': 0, + 'boxShadowVertical': 0, + 'hasCustomCSS': false + }, + 'innerBlocks': [] + }, + { + 'clientId': '82ce962e-2d52-4982-b1a1-fa01a416f5fe', + 'name': 'core/heading', + 'isValid': true, + 'originalContent': '

Style Variations

', + 'validationIssues': [], + 'attributes': { + 'content': 'Style Variations', + 'level': 3, + 'otterConditions': [], + 'hasCustomCSS': false + }, + 'innerBlocks': [] + }, + { + 'clientId': 'e037ae68-5b36-4268-a117-be45481b932d', + 'name': 'core/paragraph', + 'isValid': true, + 'originalContent': '

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.

', + 'validationIssues': [], + 'attributes': { + 'content': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.', + 'dropCap': false, + 'hasCustomCSS': false, + 'otterConditions': [] + }, + 'innerBlocks': [] + } + ] + }, + { + 'clientId': '69e9e1c2-dca1-4050-9dd9-aba5bf622ec9', + 'name': 'core/column', + 'isValid': true, + 'originalContent': '
', + 'validationIssues': [], + 'attributes': { + 'hasCustomCSS': false, + 'otterConditions': [] + }, + 'innerBlocks': [ + { + 'clientId': '136177ea-91b8-4970-8dbf-9eaa5af466fe', + 'name': 'core/image', + 'isValid': true, + 'originalContent': '
Illustration
', + 'validationIssues': [], + 'attributes': { + 'url': 'http://localhost:8888/wp-content/themes/raft/assets/img/shape-06.svg', + 'alt': 'Illustration', + 'caption': [], + 'linkDestination': 'none', + 'className': 'size-full', + 'otterConditions': [], + 'boxShadow': false, + 'boxShadowColor': '#000000', + 'boxShadowColorOpacity': 50, + 'boxShadowBlur': 5, + 'boxShadowHorizontal': 0, + 'boxShadowVertical': 0, + 'hasCustomCSS': false + }, + 'innerBlocks': [] + }, + { + 'clientId': '60c0d2dc-c526-475f-9cbf-5441f67b2d7d', + 'name': 'core/heading', + 'isValid': true, + 'originalContent': '

Built-in Patterns

', + 'validationIssues': [], + 'attributes': { + 'content': 'Built-in Patterns', + 'level': 3, + 'otterConditions': [], + 'hasCustomCSS': false + }, + 'innerBlocks': [] + }, + { + 'clientId': '526feaf1-96d1-49f2-92a5-20377d40695c', + 'name': 'core/paragraph', + 'isValid': true, + 'originalContent': '

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.

', + 'validationIssues': [], + 'attributes': { + 'content': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.', + 'dropCap': false, + 'hasCustomCSS': false, + 'otterConditions': [] + }, + 'innerBlocks': [] + } + ] + }, + { + 'clientId': '94fe896c-d8ec-4311-ab2d-58ed6d7dfd6b', + 'name': 'core/column', + 'isValid': true, + 'originalContent': '
', + 'validationIssues': [], + 'attributes': { + 'hasCustomCSS': false, + 'otterConditions': [] + }, + 'innerBlocks': [ + { + 'clientId': '601b19bf-e72a-44e8-aab0-e6622d4c7299', + 'name': 'core/image', + 'isValid': true, + 'originalContent': '
Illustration
', + 'validationIssues': [], + 'attributes': { + 'url': 'http://localhost:8888/wp-content/themes/raft/assets/img/shape-04.svg', + 'alt': 'Illustration', + 'caption': [], + 'linkDestination': 'none', + 'className': 'size-full', + 'otterConditions': [], + 'boxShadow': false, + 'boxShadowColor': '#000000', + 'boxShadowColorOpacity': 50, + 'boxShadowBlur': 5, + 'boxShadowHorizontal': 0, + 'boxShadowVertical': 0, + 'hasCustomCSS': false + }, + 'innerBlocks': [] + }, + { + 'clientId': 'fbf0ab73-317b-474e-b1ad-e235e202f782', + 'name': 'core/heading', + 'isValid': true, + 'originalContent': '

Powered by blocks

', + 'validationIssues': [], + 'attributes': { + 'content': 'Powered by blocks', + 'level': 3, + 'otterConditions': [], + 'hasCustomCSS': false + }, + 'innerBlocks': [] + }, + { + 'clientId': 'b0eebc71-e75c-4371-ba85-372de6b1e137', + 'name': 'core/paragraph', + 'isValid': true, + 'originalContent': '

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.

', + 'validationIssues': [], + 'attributes': { + 'content': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.', + 'dropCap': false, + 'hasCustomCSS': false, + 'otterConditions': [] + }, + 'innerBlocks': [] + } + ] + } + ] + } + ] + }, + { + 'clientId': '2d130305-7402-4f5c-bb48-35303f346d69', + 'name': 'core/group', + 'isValid': true, + 'originalContent': '
\n\n\n\n
', + 'validationIssues': [], + 'attributes': { + 'tagName': 'div', + 'align': 'full', + 'backgroundColor': 'raft-accent', + 'textColor': 'raft-fg-alt', + 'style': { + 'spacing': { + 'padding': { + 'top': '64px', + 'bottom': '64px' + }, + 'blockGap': '40px', + 'margin': { + 'top': '0px', + 'bottom': '0px' + } + } + }, + 'layout': { + 'inherit': true, + 'type': 'constrained' + }, + 'otterConditions': [], + 'hasCustomCSS': false + }, + 'innerBlocks': [ + { + 'clientId': '4004c3dd-db04-4c6e-ad21-6211a096c172', + 'name': 'core/heading', + 'isValid': true, + 'originalContent': '

Lorem ipsum sit dolor!

', + 'validationIssues': [], + 'attributes': { + 'textAlign': 'center', + 'content': 'Lorem ipsum sit dolor!', + 'level': 2, + 'textColor': 'raft-fg-alt', + 'otterConditions': [], + 'hasCustomCSS': false + }, + 'innerBlocks': [] + }, + { + 'clientId': '53ebe99e-ce77-45e8-8f93-3b48a3937ce6', + 'name': 'core/buttons', + 'isValid': true, + 'originalContent': '
\n\n
', + 'validationIssues': [], + 'attributes': { + 'layout': { + 'type': 'flex', + 'justifyContent': 'center' + }, + 'hasCustomCSS': false, + 'otterConditions': [] + }, + 'innerBlocks': [ + { + 'clientId': '6816ab82-b189-47a2-972e-4a1fa42e84b2', + 'name': 'core/button', + 'isValid': true, + 'originalContent': '
Lorem ipsum
', + 'validationIssues': [], + 'attributes': { + 'text': 'Lorem ipsum', + 'textColor': 'raft-fg-alt', + 'className': 'is-style-outline', + 'otterConditions': [], + 'hasCustomCSS': false + }, + 'innerBlocks': [] + } + ] + } + ] + }, + { + 'clientId': '9819ba4d-2fb6-4051-be88-211539c8ca50', + 'name': 'core/template-part', + 'isValid': true, + 'originalContent': '', + 'validationIssues': [], + 'attributes': { + 'slug': 'footer', + 'theme': 'raft', + 'tagName': 'footer', + 'hasCustomCSS': false, + 'otterConditions': [] + }, + 'innerBlocks': [] + } +]; + +const App = () => { + return ( +
+
+ + +
+ +
+
+
+ ); +}; + +export default App; diff --git a/src/onboarding/components/PalettePreview.js b/src/onboarding/components/PalettePreview.js new file mode 100644 index 000000000..5c28d2d68 --- /dev/null +++ b/src/onboarding/components/PalettePreview.js @@ -0,0 +1,133 @@ +/** + * External dependencies. + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies. + */ +import { __ } from '@wordpress/i18n'; + +import { + __unstableMotion as motion +} from '@wordpress/components'; + +import { useReducedMotion } from '@wordpress/compose'; + +import { + useEffect, + useState +} from '@wordpress/element'; + + +const firstFrame = { + start: { + scale: 1, + opacity: 1 + }, + hover: { + scale: 0, + opacity: 0 + } +}; + +const secondFrame = { + hover: { + scale: 1, + opacity: 1 + }, + start: { + scale: 0, + opacity: 0 + } +}; + +const PalettePreview = ({ + title, + isSelected, + backgroundColor, + typographyColor, + backgroundAltColor, + primaryColor, + typography, + onSelect +}) => { + const [ typographyStyles, setTypographyStyles ] = useState({}); + const [ isHovered, setIsHovered ] = useState( false ); + + const disableMotion = useReducedMotion(); + + useEffect( () => { + const styles = {}; + + if ( typography?.fontFamily ) { + styles.fontFamily = typography.fontFamily; + } + + setTypographyStyles( styles ); + }, []); + + return ( +
setIsHovered( true ) } + onMouseLeave={ () => setIsHovered( false ) } + > + + +
+ { __( 'Aa', 'otter-blocks' ) } +
+ +
+
+ +
+
+ + + + { title } + + +
+ ); +}; + +export default PalettePreview; diff --git a/src/onboarding/components/Sidebar.js b/src/onboarding/components/Sidebar.js new file mode 100644 index 000000000..7fb77af33 --- /dev/null +++ b/src/onboarding/components/Sidebar.js @@ -0,0 +1,119 @@ +/** + * WordPress dependencies. + */ +import { __ } from '@wordpress/i18n'; + +import { Button } from '@wordpress/components'; + +import { + useDispatch, + useSelect +} from '@wordpress/data'; + +/** + * Internal dependencies. + */ +import SiteInfo from './steps/SiteInfo'; +import Appearance from './steps/Appearance'; + +const STEP_DATA = { + 'site_info': { + title: __( 'Add your site\'s info', 'otter-blocks' ), + description: __( 'Add your site title and a logo. No logo yet? No worries, you can add one later.', 'otter-blocks' ), + controls: SiteInfo + }, + 'appearance': { + title: __( 'Edit site\'s appearance', 'otter-blocks' ), + description: __( 'Change the appearance of your entire site in minutes, by choosing a theme style preset.', 'otter-blocks' ), + controls: Appearance + }, + 'blog_template': { + title: __( 'Select a template for your Blog Page', 'otter-blocks' ), + description: __( 'Choose a layout for for how your blog posts appear in the blog page.', 'otter-blocks' ) + }, + 'single_template': { + title: __( 'Select a template for your Single Posts', 'otter-blocks' ), + description: __( 'Choose a layout for your single posts', 'otter-blocks' ) + }, + 'additional_templates': { + title: __( 'Add additional pages to your site', 'otter-blocks' ), + description: __( 'Create additional pages to your website.', 'otter-blocks' ) + } +}; + +const Sidebar = () => { + const { + currentStep, + stepIndex + } = useSelect( select => { + const { getStep } = select( 'otter/onboarding' ); + + return { + currentStep: getStep()?.id, + stepIndex: getStep()?.value + }; + }); + + const { nextStep, previousStep } = useDispatch( 'otter/onboarding' ); + + const Controls = STEP_DATA[ currentStep ]?.controls || null; + + const onExit = () => { + const node = document.getElementById( 'otter-onboarding' ); + node.remove(); + }; + + return ( +
+
+ + + { 1 !== stepIndex ? ( + + ) : ( + + ) } +
+ +
+
+

{ STEP_DATA[ currentStep ]?.title }

+

{ STEP_DATA[ currentStep ]?.description }

+
+ + { Controls && } +
+ +
+ + + +
+
+ ); +}; + +export default Sidebar; diff --git a/src/onboarding/components/steps/Appearance.js b/src/onboarding/components/steps/Appearance.js new file mode 100644 index 000000000..3ca8a59da --- /dev/null +++ b/src/onboarding/components/steps/Appearance.js @@ -0,0 +1,50 @@ +/** + * WordPress dependencies. + */ +import { __ } from '@wordpress/i18n'; + +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies. + */ +import PalettePreview from '../PalettePreview'; + +const { defaultStyles, themeStyles } = window.otterObj; + +const Appearance = () => { + const [ palette, setPalette ] = useState( '' ); + + return ( +
+
+ setPalette( 'default' ) } + /> + + { themeStyles.map( ( style, index ) => ( + setPalette( index ) } + /> + ) ) } +
+
+ ); +}; + +export default Appearance; diff --git a/src/onboarding/components/steps/SiteInfo.js b/src/onboarding/components/steps/SiteInfo.js new file mode 100644 index 000000000..7fe7cffe2 --- /dev/null +++ b/src/onboarding/components/steps/SiteInfo.js @@ -0,0 +1,97 @@ +/** + * WordPress dependencies. + */ +import { __ } from '@wordpress/i18n'; + +import { + BaseControl, + Button, + TextControl +} from '@wordpress/components'; + +import { useState } from '@wordpress/element'; + +import { addFilter } from '@wordpress/hooks'; + +import { MediaUpload } from '@wordpress/media-utils'; + +const SiteInfo = () => { + const [ siteTitle, setSiteTitle ] = useState( '' ); + const [ siteLogo, setSiteLogo ] = useState({}); + + const replaceMediaUpload = () => MediaUpload; + + addFilter( + 'editor.MediaUpload', + 'themeisle-blocks/onboarding/replace-media-upload', + replaceMediaUpload() + ); + + return ( +
+ + + + ( + <> + { ! siteLogo?.id && ( +
+ { __( 'Select or upload image', 'otter-blocks' ) } +
+ ) } + + { siteLogo?.id && ( + <> +
+ { +
+ +
+ + + +
+ + ) } + + ) } + /> +
+
+ ); +}; + +export default SiteInfo; diff --git a/src/onboarding/index.js b/src/onboarding/index.js new file mode 100644 index 000000000..f03989c0c --- /dev/null +++ b/src/onboarding/index.js @@ -0,0 +1,27 @@ +/** + * WordPress dependencies. + */ +import { createPortal } from '@wordpress/element'; + +import { registerPlugin } from '@wordpress/plugins'; + +/** + * Internal dependencies. + */ +import './style.scss'; +import './store'; +import { generateStylesheet } from './utils'; +import App from './components/App'; + +const Render = () => { + generateStylesheet(); + + return createPortal( + , + document.body + ); +}; + +registerPlugin( 'otter-onboarding', { + render: Render +}); diff --git a/src/onboarding/store.js b/src/onboarding/store.js new file mode 100644 index 000000000..9073f6883 --- /dev/null +++ b/src/onboarding/store.js @@ -0,0 +1,83 @@ +/** + * WordPress dependencies. + */ +import { + createReduxStore, + register +} from '@wordpress/data'; + +const STEPS = [ + { + id: 'site_info', + value: 1 + }, + { + id: 'appearance', + value: 2 + }, + { + id: 'blog_template', + value: 3 + }, + { + id: 'single_template', + value: 4 + }, + { + id: 'additional_templates', + value: 5 + } +]; + +const DEFAULT_STATE = { + step: 1 +}; + +const actions = { + setStep( step ) { + return { + type: 'SET_STEP', + step + }; + }, + nextStep() { + return ({ dispatch, select }) => { + const step = select.getStep(); + const newStep = STEPS.length < ( step.value + 1 ) ? STEPS.length : ( step.value + 1 ); + + dispatch( actions.setStep( newStep ) ); + }; + }, + previousStep() { + return ({ dispatch, select }) => { + const step = select.getStep(); + const newStep = 1 > ( step.value - 1 ) ? 1 : ( step.value - 1 ); + + dispatch( actions.setStep( newStep ) ); + }; + } +}; + +const store = createReduxStore( 'otter/onboarding', { + reducer( state = DEFAULT_STATE, action ) { + switch ( action.type ) { + case 'SET_STEP': + return { + ...state, + step: action.step + }; + } + + return state; + }, + + actions, + + selectors: { + getStep( state ) { + return STEPS.find( step => step.value === state.step ); + } + } +}); + +register( store ); diff --git a/src/onboarding/style.scss b/src/onboarding/style.scss new file mode 100644 index 000000000..255af560b --- /dev/null +++ b/src/onboarding/style.scss @@ -0,0 +1,239 @@ +$main-bg: #F4F4F4; +$secondary-text: #848484; +$main-color: #151414; +$border-color: #E5E5E5; + +div.error, div.notice { + display: none; +} + +#otter-onboarding { + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 100000; + background: $main-bg; + overflow: auto; +} + +.o-onboarding { + display: grid; + grid-template-columns: 1fr 3fr; + width: 100vw; + height: 100vh; + font-family: Arial,sans-serif; + --wp-components-color-accent: #0366D5; +} + +.o-sidebar { + background: #fff; + box-shadow: 0 0 10px rgba(0,0,0,.1); + display: flex; + flex-direction: column; + overflow: scroll; + + &__header { + padding: 2rem 2rem 0 2rem; + display: flex; + justify-content: space-between; + align-items: center; + } + + &__logo { + width: 64px; + height: 64px; + } + + &__content { + flex: 1; + padding: 0 2rem 2rem 2rem; + } + + &__info { + padding: 2rem 0 4rem 0; + + h2 { + color: $main-color; + margin: 0 0 1rem 0; + font-size: 24px; + font-style: normal; + font-weight: 700; + line-height: 150%; + } + + p { + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 170%; + } + } + + &__controls { + display: flex; + flex-direction: column; + gap: 20px; + + .components-base-control__label { + color: $main-color; + font-size: 20px; + font-style: normal; + font-weight: 700; + line-height: 150%; + text-transform: none; + } + + .components-text-control__input { + border-radius: 6px; + border: 1px solid $border-color; + background: #FFF; + padding: 0.8rem 1.2rem; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 170%; + + &::placeholder { + color: $secondary-text; + } + } + } + + &__actions { + background: #fff; + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; + position: sticky; + bottom: 0; + padding: 2rem; + + .components-button.is-primary { + padding: 16px 32px; + justify-content: center; + align-items: center; + gap: 32px; + border-radius: 5px; + width: 100%; + font-size: 18px; + font-style: normal; + font-weight: 700; + height: auto; + } + + .components-button.is-tertiary { + font-size: 14px; + font-style: normal; + font-weight: 400; + --wp-components-color-accent: $secondary-text; + } + } + + .o-logo { + &__placeholder { + display: flex; + align-items: center; + justify-content: center; + gap: 32px; + border-radius: 8px; + border: 1px dashed $secondary-text; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 170%; + height: 150px; + cursor: pointer; + } + + &__image { + max-height: 150px; + cursor: pointer; + display: flex; + flex-direction: column; + align-items: center; + + img { + width: 100%; + max-height: 150px; + object-fit: contain; + } + + &__controls { + display: flex; + gap: 8px; + margin-top: 16px; + } + } + } + + .o-palettes { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; + + .o-palette { + border-radius: 2px; + box-shadow: 0 0 0 1px #e0e0e0; + outline: 1px solid #f000; + padding: 2px; + position: relative; + aspect-ratio: 7 / 4; + cursor: pointer; + + &:hover { + outline: 2px solid var( --wp-components-color-accent ); + } + + &.is-selected { + outline: 2px solid var( --wp-components-color-accent ); + } + + &__area { + height: 100%; + display: flex; + justify-content: center; + align-items: center; + } + + &__preview { + display: flex; + gap: 20px; + align-items: center; + position: absolute; + } + + &__label { + font-size: 1.2vw; + font-weight: 600; + line-height: 1em; + text-align: center; + } + + &__typography { + font-size: 2.6vw; + font-style: normal; + font-weight: 700; + line-height: 150%; + } + + &__colors { + display: flex; + flex-direction: column; + gap: 8px; + } + + &__color { + height: 20px; + width: 20px; + border-radius: 16px; + opacity: 1; + transform: none; + } + } + } +} + +.o-main {} diff --git a/src/onboarding/utils.js b/src/onboarding/utils.js new file mode 100644 index 000000000..e93824685 --- /dev/null +++ b/src/onboarding/utils.js @@ -0,0 +1,18 @@ +const generateFontVariables = () => { + const { fontFamilies } = window.otterObj.defaultStyles.settings.settings.typography; + + let fontVariables = []; + + fontFamilies.forEach( ( font, index ) => { + fontVariables.push( `--wp--preset--font-family--${ font.slug }: ${ font.fontFamily };` ); + }); + + return fontVariables; +}; + +export const generateStylesheet = () => { + const fontVariables = generateFontVariables(); + const style = document.createElement( 'style' ); + style.innerHTML = `.o-onboarding { ${ fontVariables.join( '' ) } }`; + document.head.appendChild( style ); +}; diff --git a/webpack.config.js b/webpack.config.js index 9eb235ce7..ec313b2a7 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -39,6 +39,26 @@ module.exports = [ }) ] }, + { + + // OTTER ONBOARDING + ...defaultConfig, + stats: 'minimal', + mode: NODE_ENV, + entry: { + index: './src/onboarding/index.js' + }, + output: { + path: path.resolve( __dirname, './build/onboarding' ) + }, + plugins: [ + ...defaultConfig.plugins, + new BundleAnalyzerPlugin({ + analyzerMode: 'disabled', + generateStatsFile: ANALYZER + }) + ] + }, { // ANIMATION @@ -175,7 +195,9 @@ module.exports = [ 'build/blocks/pro/', 'build/blocks/blocks/', 'build/pro/pro', - 'build/pro/blocks' + 'build/pro/blocks', + 'build/onboarding/blocks/', + 'build/onboarding/pro/' ] } }, From 6d0df632d44d0c5a5b26debb87d2b6b0938a32f2 Mon Sep 17 00:00:00 2001 From: Hardeep Asrani Date: Wed, 22 Nov 2023 09:40:14 +0530 Subject: [PATCH 02/37] Add Homepage Preview --- src/onboarding/components/App.js | 546 +------------------- src/onboarding/components/Main.js | 59 +++ src/onboarding/components/Sidebar.js | 16 +- src/onboarding/components/steps/Homepage.js | 29 ++ src/onboarding/components/steps/SiteInfo.js | 116 ++++- src/onboarding/hooks/index.js | 111 ++++ src/onboarding/store.js | 69 ++- src/onboarding/style.scss | 25 +- 8 files changed, 416 insertions(+), 555 deletions(-) create mode 100644 src/onboarding/components/Main.js create mode 100644 src/onboarding/components/steps/Homepage.js create mode 100644 src/onboarding/hooks/index.js diff --git a/src/onboarding/components/App.js b/src/onboarding/components/App.js index 72ae921e9..2f6bb50bb 100644 --- a/src/onboarding/components/App.js +++ b/src/onboarding/components/App.js @@ -3,552 +3,26 @@ */ import { __ } from '@wordpress/i18n'; -import { BlockPreview } from '@wordpress/block-editor'; - /** * Internal dependencies. */ import Sidebar from './Sidebar'; - -const blocks = [ - { - 'clientId': '7de83d98-f17a-4f5f-8873-da7b229d990f', - 'name': 'core/template-part', - 'isValid': true, - 'originalContent': '', - 'validationIssues': [], - 'attributes': { - 'slug': 'header', - 'theme': 'raft', - 'tagName': 'header', - 'hasCustomCSS': false, - 'otterConditions': [] - }, - 'innerBlocks': [] - }, - { - 'clientId': '032637d0-12ee-470b-b5cc-84e46f1dce66', - 'name': 'core/group', - 'isValid': true, - 'originalContent': '
\n\n\n\n\n\n\n\n\n\n\n\n
', - 'validationIssues': [], - 'attributes': { - 'tagName': 'div', - 'backgroundColor': 'raft-bg-alt', - 'style': { - 'spacing': { - 'padding': { - 'top': '64px', - 'bottom': '64px' - }, - 'blockGap': '24px', - 'margin': { - 'top': '0px', - 'bottom': '0px' - } - } - }, - 'layout': { - 'inherit': true, - 'type': 'constrained' - }, - 'otterConditions': [], - 'hasCustomCSS': false - }, - 'innerBlocks': [ - { - 'clientId': '36ab4e10-5eec-4633-a144-b09208a04fae', - 'name': 'core/heading', - 'isValid': true, - 'originalContent': '

Block-based websites made simple.

', - 'validationIssues': [], - 'attributes': { - 'textAlign': 'center', - 'content': 'Block-based websites made simple.', - 'level': 1, - 'fontSize': 'huge', - 'otterConditions': [], - 'hasCustomCSS': false - }, - 'innerBlocks': [] - }, - { - 'clientId': '8bc5b509-0cf3-4b6e-bada-79cbfe1a6542', - 'name': 'core/paragraph', - 'isValid': true, - 'originalContent': '

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor ut labore et dolore magna aliqua.

', - 'validationIssues': [], - 'attributes': { - 'align': 'center', - 'content': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor ut labore et dolore magna aliqua.', - 'dropCap': false, - 'fontSize': 'medium', - 'hasCustomCSS': false, - 'otterConditions': [] - }, - 'innerBlocks': [] - }, - { - 'clientId': '21111f4a-9696-43de-8243-8b49ca8a068f', - 'name': 'core/buttons', - 'isValid': true, - 'originalContent': '
\n\n\n\n
', - 'validationIssues': [], - 'attributes': { - 'layout': { - 'type': 'flex', - 'justifyContent': 'center' - }, - 'hasCustomCSS': false, - 'otterConditions': [] - }, - 'innerBlocks': [ - { - 'clientId': '642d719f-b419-4b2d-8a61-9624cf92b595', - 'name': 'core/button', - 'isValid': true, - 'originalContent': '', - 'validationIssues': [], - 'attributes': { - 'text': 'Learn More', - 'textColor': 'raft-fg-alt', - 'otterConditions': [], - 'hasCustomCSS': false - }, - 'innerBlocks': [] - } - ] - }, - { - 'clientId': '214e3be8-fd03-4d9d-8747-da041dc79ef3', - 'name': 'core/spacer', - 'isValid': true, - 'originalContent': '', - 'validationIssues': [], - 'attributes': { - 'height': '3vw', - 'hasCustomCSS': false, - 'otterConditions': [] - }, - 'innerBlocks': [] - }, - { - 'clientId': '757d828c-1fa0-49ae-af42-87a2833c6368', - 'name': 'core/image', - 'isValid': true, - 'originalContent': '
\nHero Illustration\n
', - 'validationIssues': [], - 'attributes': { - 'align': 'center', - 'url': 'http://localhost:8888/wp-content/themes/raft/assets/img/raft-illustration.svg', - 'alt': 'Hero Illustration', - 'caption': '', - 'sizeSlug': 'large', - 'linkDestination': 'none', - 'hasCustomCSS': false, - 'otterConditions': [], - 'boxShadow': false, - 'boxShadowColor': '#000000', - 'boxShadowColorOpacity': 50, - 'boxShadowBlur': 5, - 'boxShadowHorizontal': 0, - 'boxShadowVertical': 0 - }, - 'innerBlocks': [] - } - ] - }, - { - 'clientId': '0dc9b2ee-ecea-4893-ac43-9ec4de5b981f', - 'name': 'core/group', - 'isValid': true, - 'originalContent': '
\n \n
', - 'validationIssues': [], - 'attributes': { - 'tagName': 'div', - 'style': { - 'spacing': { - 'padding': { - 'top': '64px', - 'bottom': '64px' - }, - 'margin': { - 'top': '0px', - 'bottom': '0px' - } - } - }, - 'layout': { - 'type': 'constrained' - }, - 'hasCustomCSS': false, - 'otterConditions': [] - }, - 'innerBlocks': [ - { - 'clientId': '002c8b37-2ece-4151-b151-6b114f00b181', - 'name': 'core/post-content', - 'isValid': true, - 'originalContent': '', - 'validationIssues': [], - 'attributes': { - 'layout': { - 'type': 'default' - }, - 'hasCustomCSS': false, - 'otterConditions': [] - }, - 'innerBlocks': [] - } - ] - }, - { - 'clientId': '7046e62b-9094-4c1e-87ac-cdc1c874cdc7', - 'name': 'core/group', - 'isValid': true, - 'originalContent': '
\n\n\n\n
', - 'validationIssues': [], - 'attributes': { - 'tagName': 'div', - 'align': 'full', - 'style': { - 'spacing': { - 'padding': { - 'top': '64px', - 'bottom': '64px' - }, - 'margin': { - 'top': '0px', - 'bottom': '0px' - } - } - }, - 'layout': { - 'type': 'constrained' - }, - 'hasCustomCSS': false, - 'otterConditions': [] - }, - 'innerBlocks': [ - { - 'clientId': '520f4d8e-1237-4920-aa5c-92718b033bd3', - 'name': 'core/columns', - 'isValid': true, - 'originalContent': '
', - 'validationIssues': [], - 'attributes': { - 'isStackedOnMobile': true, - 'align': 'wide', - 'hasCustomCSS': false, - 'otterConditions': [] - }, - 'innerBlocks': [ - { - 'clientId': '95527c39-32d2-4e9a-8a16-53e4c3e2b893', - 'name': 'core/column', - 'isValid': true, - 'originalContent': '
', - 'validationIssues': [], - 'attributes': { - 'hasCustomCSS': false, - 'otterConditions': [] - }, - 'innerBlocks': [ - { - 'clientId': 'c12b5e3f-6513-4a23-be28-055e2fc7da9c', - 'name': 'core/image', - 'isValid': true, - 'originalContent': '
Illustration
', - 'validationIssues': [], - 'attributes': { - 'url': 'http://localhost:8888/wp-content/themes/raft/assets/img/shape-05.svg', - 'alt': 'Illustration', - 'caption': [], - 'linkDestination': 'none', - 'className': 'size-full', - 'otterConditions': [], - 'boxShadow': false, - 'boxShadowColor': '#000000', - 'boxShadowColorOpacity': 50, - 'boxShadowBlur': 5, - 'boxShadowHorizontal': 0, - 'boxShadowVertical': 0, - 'hasCustomCSS': false - }, - 'innerBlocks': [] - }, - { - 'clientId': '82ce962e-2d52-4982-b1a1-fa01a416f5fe', - 'name': 'core/heading', - 'isValid': true, - 'originalContent': '

Style Variations

', - 'validationIssues': [], - 'attributes': { - 'content': 'Style Variations', - 'level': 3, - 'otterConditions': [], - 'hasCustomCSS': false - }, - 'innerBlocks': [] - }, - { - 'clientId': 'e037ae68-5b36-4268-a117-be45481b932d', - 'name': 'core/paragraph', - 'isValid': true, - 'originalContent': '

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.

', - 'validationIssues': [], - 'attributes': { - 'content': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.', - 'dropCap': false, - 'hasCustomCSS': false, - 'otterConditions': [] - }, - 'innerBlocks': [] - } - ] - }, - { - 'clientId': '69e9e1c2-dca1-4050-9dd9-aba5bf622ec9', - 'name': 'core/column', - 'isValid': true, - 'originalContent': '
', - 'validationIssues': [], - 'attributes': { - 'hasCustomCSS': false, - 'otterConditions': [] - }, - 'innerBlocks': [ - { - 'clientId': '136177ea-91b8-4970-8dbf-9eaa5af466fe', - 'name': 'core/image', - 'isValid': true, - 'originalContent': '
Illustration
', - 'validationIssues': [], - 'attributes': { - 'url': 'http://localhost:8888/wp-content/themes/raft/assets/img/shape-06.svg', - 'alt': 'Illustration', - 'caption': [], - 'linkDestination': 'none', - 'className': 'size-full', - 'otterConditions': [], - 'boxShadow': false, - 'boxShadowColor': '#000000', - 'boxShadowColorOpacity': 50, - 'boxShadowBlur': 5, - 'boxShadowHorizontal': 0, - 'boxShadowVertical': 0, - 'hasCustomCSS': false - }, - 'innerBlocks': [] - }, - { - 'clientId': '60c0d2dc-c526-475f-9cbf-5441f67b2d7d', - 'name': 'core/heading', - 'isValid': true, - 'originalContent': '

Built-in Patterns

', - 'validationIssues': [], - 'attributes': { - 'content': 'Built-in Patterns', - 'level': 3, - 'otterConditions': [], - 'hasCustomCSS': false - }, - 'innerBlocks': [] - }, - { - 'clientId': '526feaf1-96d1-49f2-92a5-20377d40695c', - 'name': 'core/paragraph', - 'isValid': true, - 'originalContent': '

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.

', - 'validationIssues': [], - 'attributes': { - 'content': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.', - 'dropCap': false, - 'hasCustomCSS': false, - 'otterConditions': [] - }, - 'innerBlocks': [] - } - ] - }, - { - 'clientId': '94fe896c-d8ec-4311-ab2d-58ed6d7dfd6b', - 'name': 'core/column', - 'isValid': true, - 'originalContent': '
', - 'validationIssues': [], - 'attributes': { - 'hasCustomCSS': false, - 'otterConditions': [] - }, - 'innerBlocks': [ - { - 'clientId': '601b19bf-e72a-44e8-aab0-e6622d4c7299', - 'name': 'core/image', - 'isValid': true, - 'originalContent': '
Illustration
', - 'validationIssues': [], - 'attributes': { - 'url': 'http://localhost:8888/wp-content/themes/raft/assets/img/shape-04.svg', - 'alt': 'Illustration', - 'caption': [], - 'linkDestination': 'none', - 'className': 'size-full', - 'otterConditions': [], - 'boxShadow': false, - 'boxShadowColor': '#000000', - 'boxShadowColorOpacity': 50, - 'boxShadowBlur': 5, - 'boxShadowHorizontal': 0, - 'boxShadowVertical': 0, - 'hasCustomCSS': false - }, - 'innerBlocks': [] - }, - { - 'clientId': 'fbf0ab73-317b-474e-b1ad-e235e202f782', - 'name': 'core/heading', - 'isValid': true, - 'originalContent': '

Powered by blocks

', - 'validationIssues': [], - 'attributes': { - 'content': 'Powered by blocks', - 'level': 3, - 'otterConditions': [], - 'hasCustomCSS': false - }, - 'innerBlocks': [] - }, - { - 'clientId': 'b0eebc71-e75c-4371-ba85-372de6b1e137', - 'name': 'core/paragraph', - 'isValid': true, - 'originalContent': '

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.

', - 'validationIssues': [], - 'attributes': { - 'content': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.', - 'dropCap': false, - 'hasCustomCSS': false, - 'otterConditions': [] - }, - 'innerBlocks': [] - } - ] - } - ] - } - ] - }, - { - 'clientId': '2d130305-7402-4f5c-bb48-35303f346d69', - 'name': 'core/group', - 'isValid': true, - 'originalContent': '
\n\n\n\n
', - 'validationIssues': [], - 'attributes': { - 'tagName': 'div', - 'align': 'full', - 'backgroundColor': 'raft-accent', - 'textColor': 'raft-fg-alt', - 'style': { - 'spacing': { - 'padding': { - 'top': '64px', - 'bottom': '64px' - }, - 'blockGap': '40px', - 'margin': { - 'top': '0px', - 'bottom': '0px' - } - } - }, - 'layout': { - 'inherit': true, - 'type': 'constrained' - }, - 'otterConditions': [], - 'hasCustomCSS': false - }, - 'innerBlocks': [ - { - 'clientId': '4004c3dd-db04-4c6e-ad21-6211a096c172', - 'name': 'core/heading', - 'isValid': true, - 'originalContent': '

Lorem ipsum sit dolor!

', - 'validationIssues': [], - 'attributes': { - 'textAlign': 'center', - 'content': 'Lorem ipsum sit dolor!', - 'level': 2, - 'textColor': 'raft-fg-alt', - 'otterConditions': [], - 'hasCustomCSS': false - }, - 'innerBlocks': [] - }, - { - 'clientId': '53ebe99e-ce77-45e8-8f93-3b48a3937ce6', - 'name': 'core/buttons', - 'isValid': true, - 'originalContent': '
\n\n
', - 'validationIssues': [], - 'attributes': { - 'layout': { - 'type': 'flex', - 'justifyContent': 'center' - }, - 'hasCustomCSS': false, - 'otterConditions': [] - }, - 'innerBlocks': [ - { - 'clientId': '6816ab82-b189-47a2-972e-4a1fa42e84b2', - 'name': 'core/button', - 'isValid': true, - 'originalContent': '', - 'validationIssues': [], - 'attributes': { - 'text': 'Lorem ipsum', - 'textColor': 'raft-fg-alt', - 'className': 'is-style-outline', - 'otterConditions': [], - 'hasCustomCSS': false - }, - 'innerBlocks': [] - } - ] - } - ] - }, - { - 'clientId': '9819ba4d-2fb6-4051-be88-211539c8ca50', - 'name': 'core/template-part', - 'isValid': true, - 'originalContent': '', - 'validationIssues': [], - 'attributes': { - 'slug': 'footer', - 'theme': 'raft', - 'tagName': 'footer', - 'hasCustomCSS': false, - 'otterConditions': [] - }, - 'innerBlocks': [] - } -]; +import Main from './Main'; +import { useIsSiteEditorLoading } from '../hooks'; const App = () => { + const isEditorLoading = useIsSiteEditorLoading(); + return (
- + -
- -
+
); diff --git a/src/onboarding/components/Main.js b/src/onboarding/components/Main.js new file mode 100644 index 000000000..00986c671 --- /dev/null +++ b/src/onboarding/components/Main.js @@ -0,0 +1,59 @@ +/** + * WordPress dependencies. + */ +import { Spinner } from '@wordpress/components'; + +import { useSelect } from '@wordpress/data'; + +/** + * Internal dependencies. + */ +import Homepage from './steps/Homepage'; + +import { useIsSiteEditorLoading } from '../hooks'; + +const STEP_DATA = { + 'site_info': { + controls: Homepage + }, + 'appearance': { + controls: Homepage + }, + 'blog_template': { + controls: Homepage + }, + 'single_template': { + controls: Homepage + }, + 'additional_templates': { + controls: Homepage + } +}; + +const Main = ({ isEditorLoading }) => { + const { currentStep } = useSelect( select => { + const { getStep } = select( 'otter/onboarding' ); + + return { + currentStep: getStep()?.id + }; + }); + + const Controls = STEP_DATA[ currentStep ]?.controls || null; + + if ( isEditorLoading ) { + return ( +
+ +
+ ); + } + + return ( +
+ +
+ ); +}; + +export default Main; diff --git a/src/onboarding/components/Sidebar.js b/src/onboarding/components/Sidebar.js index 7fb77af33..5b92dd50f 100644 --- a/src/onboarding/components/Sidebar.js +++ b/src/onboarding/components/Sidebar.js @@ -3,7 +3,11 @@ */ import { __ } from '@wordpress/i18n'; -import { Button } from '@wordpress/components'; +import { + Button, + Disabled, + Spinner +} from '@wordpress/components'; import { useDispatch, @@ -41,7 +45,7 @@ const STEP_DATA = { } }; -const Sidebar = () => { +const Sidebar = ({ isEditorLoading }) => { const { currentStep, stepIndex @@ -65,6 +69,14 @@ const Sidebar = () => { return (
+ { ( isEditorLoading ) && ( +
+ + + +
+ ) } +
{ + const { template } = useSelect( select => { + const { getTemplate } = select( 'otter/onboarding' ); + + const template = getTemplate({ slug: 'front-page' }); + const parsedTemplate = template?.content?.raw ? parse( template?.content?.raw ) : []; + + return { + template: parsedTemplate + }; + }); + + return ( + + ); +}; + +export default Homepage; diff --git a/src/onboarding/components/steps/SiteInfo.js b/src/onboarding/components/steps/SiteInfo.js index 7fe7cffe2..9f24f7c6e 100644 --- a/src/onboarding/components/steps/SiteInfo.js +++ b/src/onboarding/components/steps/SiteInfo.js @@ -9,9 +9,13 @@ import { TextControl } from '@wordpress/components'; -import { useState } from '@wordpress/element'; +import { + useDispatch, + useSelect, + select +} from '@wordpress/data'; -import { addFilter } from '@wordpress/hooks'; +import { useState } from '@wordpress/element'; import { MediaUpload } from '@wordpress/media-utils'; @@ -19,28 +23,110 @@ const SiteInfo = () => { const [ siteTitle, setSiteTitle ] = useState( '' ); const [ siteLogo, setSiteLogo ] = useState({}); - const replaceMediaUpload = () => MediaUpload; + const { title } = useSelect( select => { + const { getEditedEntityRecord } = select( 'core' ); - addFilter( - 'editor.MediaUpload', - 'themeisle-blocks/onboarding/replace-media-upload', - replaceMediaUpload() - ); + const settings = getEditedEntityRecord( 'root', 'site' ); + + return { + title: settings?.title + }; + }, []); + + const { editEntityRecord } = useDispatch( 'core' ); + const { replaceBlock } = useDispatch( 'core/block-editor' ); + const { setEditedEntity } = useDispatch( 'core/edit-site' ); + + const setTitle = newTitle => { + editEntityRecord( 'root', 'site', undefined, { + title: newTitle + }); + }; + + const findBlock = ( blocksAr, name ) => { + const foundBlock = blocksAr.find( block => block.name === name ); + + if ( foundBlock ) { + return foundBlock; + } + + return blocksAr.reduce( ( found, block ) => { + if ( found ) { + return found; + } + + if ( block.innerBlocks && Array.isArray( block.innerBlocks ) ) { + return findBlock( block.innerBlocks, name ); + } + + return undefined; + }, undefined ); + }; + + const onOpenMedia = open => { + setEditedEntity( 'wp_template_part', 'raft//header', 'edit' ); + open(); + }; + + const setLogo = ( newLogo ) => { + const blocks = select( 'core/block-editor' ).getBlocks(); + + let siteLogoBlock = findBlock( blocks, 'core/site-logo' ); + + if ( ! siteLogoBlock ) { + const siteTitleBlock = findBlock( blocks, 'core/site-title' ); + + if ( siteTitleBlock ) { + + replaceBlock( siteTitleBlock.clientId, wp.blocks.createBlock( + 'core/site-logo', + { + attributes: { + ...siteTitleBlock.attributes, + url: newLogo.url, + id: newLogo.id + } + } + ) ); + } + } + + setSiteLogo( newLogo ); + }; + + const removeLogo = () => { + const blocks = select( 'core/block-editor' ).getBlocks(); + + let siteLogoBlock = findBlock( blocks, 'core/site-logo' ); + + if ( siteLogoBlock ) { + replaceBlock( siteLogoBlock.clientId, wp.blocks.createBlock( + 'core/site-title', + { + attributes: { + ...siteLogoBlock.attributes + } + } + ) ); + } + + setSiteLogo({}); + }; return (
( @@ -48,7 +134,7 @@ const SiteInfo = () => { { ! siteLogo?.id && (
onOpenMedia( open ) } > { __( 'Select or upload image', 'otter-blocks' ) }
@@ -58,7 +144,7 @@ const SiteInfo = () => { <>
onOpenMedia( open ) } > { > diff --git a/src/onboarding/hooks/index.js b/src/onboarding/hooks/index.js new file mode 100644 index 000000000..5ac46813e --- /dev/null +++ b/src/onboarding/hooks/index.js @@ -0,0 +1,111 @@ +/** + * WordPress dependencies + */ +import { useEffect, useState } from '@wordpress/element'; +import { useSelect } from '@wordpress/data'; +import { decodeEntities } from '@wordpress/html-entities'; + +const MAX_LOADING_TIME = 10000; // 10 seconds + +// This is a copy of the useEditedEntityRecord function from the Gutenberg plugin. +export const useEditedEntityRecord = ( postType, postId ) => { + const { record, title, description, isLoaded, icon } = useSelect( + ( select ) => { + const { getEditedPostType, getEditedPostId } = + select( 'core/edit-site' ); + const { getEditedEntityRecord, hasFinishedResolution } = + select( 'core' ); + const { __experimentalGetTemplateInfo: getTemplateInfo } = + select( 'core/editor' ); + const usedPostType = postType ?? getEditedPostType(); + const usedPostId = postId ?? getEditedPostId(); + const _record = getEditedEntityRecord( + 'postType', + usedPostType, + usedPostId + ); + const _isLoaded = + usedPostId && + hasFinishedResolution( 'getEditedEntityRecord', [ + 'postType', + usedPostType, + usedPostId + ]); + const templateInfo = getTemplateInfo( _record ); + + return { + record: _record, + title: templateInfo.title, + description: templateInfo.description, + isLoaded: _isLoaded, + icon: templateInfo.icon + }; + }, + [ postType, postId ] + ); + + return { + isLoaded, + icon, + record, + getTitle: () => ( title ? decodeEntities( title ) : null ), + getDescription: () => + description ? decodeEntities( description ) : null + }; +}; + +// This is a copy of the useIsSiteEditorLoading function from the Gutenberg plugin. +export const useIsSiteEditorLoading = () => { + const { isLoaded: hasLoadedPost } = useEditedEntityRecord(); + const [ loaded, setLoaded ] = useState( false ); + const inLoadingPause = useSelect( + ( select ) => { + const hasResolvingSelectors = + select( 'core' ).hasResolvingSelectors(); + return ! loaded && ! hasResolvingSelectors; + }, + [ loaded ] + ); + + /* + * If the maximum expected loading time has passed, we're marking the + * editor as loaded, in order to prevent any failed requests from blocking + * the editor canvas from appearing. + */ + useEffect( () => { + let timeout; + + if ( ! loaded ) { + timeout = setTimeout( () => { + setLoaded( true ); + }, MAX_LOADING_TIME ); + } + + return () => { + clearTimeout( timeout ); + }; + }, [ loaded ]); + + useEffect( () => { + if ( inLoadingPause ) { + + /* + * We're using an arbitrary 1s timeout here to catch brief moments + * without any resolving selectors that would result in displaying + * brief flickers of loading state and loaded state. + * + * It's worth experimenting with different values, since this also + * adds 1s of artificial delay after loading has finished. + */ + const timeout = setTimeout( () => { + setLoaded( true ); + }, 1000 ); + + return () => { + clearTimeout( timeout ); + }; + } + }, [ inLoadingPause ]); + + return ! loaded || ! hasLoadedPost; +}; diff --git a/src/onboarding/store.js b/src/onboarding/store.js index 9073f6883..b6cdf921c 100644 --- a/src/onboarding/store.js +++ b/src/onboarding/store.js @@ -1,11 +1,15 @@ /** * WordPress dependencies. */ +import apiFetch from '@wordpress/api-fetch'; + import { createReduxStore, register } from '@wordpress/data'; +import { addQueryArgs } from '@wordpress/url'; + const STEPS = [ { id: 'site_info', @@ -30,7 +34,9 @@ const STEPS = [ ]; const DEFAULT_STATE = { - step: 1 + step: 1, + templates: {}, + templateParts: {} }; const actions = { @@ -55,6 +61,24 @@ const actions = { dispatch( actions.setStep( newStep ) ); }; + }, + setTemplate( template ) { + return { + type: 'SET_TEMPLATE', + template + }; + }, + setTemplatePart( templatePart ) { + return { + type: 'SET_TEMPLATE_PART', + templatePart + }; + }, + fetchFromAPI( path ) { + return { + type: 'FETCH_FROM_API', + path + }; } }; @@ -66,6 +90,24 @@ const store = createReduxStore( 'otter/onboarding', { ...state, step: action.step }; + + case 'SET_TEMPLATE': + return { + ...state, + templates: { + ...state.templates, + [action.template.slug]: action.template + } + }; + + case 'SET_TEMPLATE_PART': + return { + ...state, + templateParts: { + ...state.templateParts, + [action.templatePart.id]: action.templatePart + } + }; } return state; @@ -76,6 +118,31 @@ const store = createReduxStore( 'otter/onboarding', { selectors: { getStep( state ) { return STEPS.find( step => step.value === state.step ); + }, + getTemplate( state, query ) { + return state.templates?.[ query?.slug ]; + }, + getTemplatePart( state, slug ) { + return state.templateParts?.[ slug ]; + } + }, + + controls: { + FETCH_FROM_API( action ) { + return apiFetch({ path: action.path }); + } + }, + + resolvers: { + *getTemplate( query ) { + const path = addQueryArgs( '/wp/v2/templates/lookup', query ); + const value = yield actions.fetchFromAPI( path ); + return actions.setTemplate( value ); + }, + *getTemplatePart( slug ) { + const path = `/wp/v2/template-parts/${ slug }` ; + const value = yield actions.fetchFromAPI( path ); + return actions.setTemplatePart( value ); } } }); diff --git a/src/onboarding/style.scss b/src/onboarding/style.scss index 255af560b..d70d8e475 100644 --- a/src/onboarding/style.scss +++ b/src/onboarding/style.scss @@ -34,6 +34,17 @@ div.error, div.notice { flex-direction: column; overflow: scroll; + &__loader { + height: 100vh; + position: absolute; + display: flex; + align-items: center; + justify-content: center; + width: 25vw; + background: rgba(255, 255, 255, 0.5); + z-index: 9; + } + &__header { padding: 2rem 2rem 0 2rem; display: flex; @@ -236,4 +247,16 @@ div.error, div.notice { } } -.o-main {} +.o-main { + height: 100vh; + overflow: scroll; +} + +.o-preview { + &__loader { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + } +} From be06bf0c968e0f94f1d4675796fe6b526c7aee54 Mon Sep 17 00:00:00 2001 From: Hardeep Asrani Date: Thu, 23 Nov 2023 00:59:07 +0530 Subject: [PATCH 03/37] Finish Site Info & Appearance UI --- inc/plugins/class-fse-onboarding.php | 12 --- package-lock.json | 24 ++--- package.json | 1 + src/onboarding/components/Sidebar.js | 18 ++-- src/onboarding/components/steps/Appearance.js | 91 ++++++++++++++++--- src/onboarding/components/steps/SiteInfo.js | 41 +++++---- src/onboarding/index.js | 3 - src/onboarding/utils.js | 18 ---- 8 files changed, 125 insertions(+), 83 deletions(-) delete mode 100644 src/onboarding/utils.js diff --git a/inc/plugins/class-fse-onboarding.php b/inc/plugins/class-fse-onboarding.php index ac2f78af2..80732cd1b 100644 --- a/inc/plugins/class-fse-onboarding.php +++ b/inc/plugins/class-fse-onboarding.php @@ -53,16 +53,6 @@ public function enqueue_options_assets() { wp_set_script_translations( 'otter-onboarding-scripts', 'otter-blocks' ); - $variations = \WP_Theme_JSON_Resolver::get_style_variations(); - - $theme_data = \WP_Theme_JSON_Resolver::get_merged_data( 'theme' ); - $data = $theme_data->get_data(); - - $default_variation = array( - 'title' => __( 'Default', 'otter-blocks' ), - 'settings' => $data, - ); - wp_localize_script( 'otter-onboarding-scripts', 'otterObj', @@ -71,8 +61,6 @@ public function enqueue_options_assets() { array( 'version' => OTTER_BLOCKS_VERSION, 'assetsPath' => OTTER_BLOCKS_URL . 'assets/', - 'themeStyles' => $variations, - 'defaultStyles' => $default_variation, ) ) ); diff --git a/package-lock.json b/package-lock.json index aaf8de728..8f91a4443 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "otter-blocks", - "version": "2.4.1", + "version": "2.5.2", "license": "GPL-2.0+", "dependencies": { "@wordpress/icons": "^9.35.0", @@ -14,6 +14,7 @@ "classnames": "^2.3.1", "currency-symbol-map": "^5.0.1", "deepmerge": "^4.3.1", + "fast-deep-equal": "^3.1.3", "hex-rgba": "^1.0.2", "object-hash": "^3.0.0", "react-sortable-hoc": "^2.0.0", @@ -14070,8 +14071,7 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-diff": { "version": "1.3.0", @@ -28233,6 +28233,15 @@ "node": ">=0.8.0" } }, + "node_modules/sockjs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/socks": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", @@ -28279,15 +28288,6 @@ "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", "dev": true }, - "node_modules/sockjs/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", diff --git a/package.json b/package.json index d665e11dd..9c54ca0c8 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "classnames": "^2.3.1", "currency-symbol-map": "^5.0.1", "deepmerge": "^4.3.1", + "fast-deep-equal": "^3.1.3", "hex-rgba": "^1.0.2", "object-hash": "^3.0.0", "react-sortable-hoc": "^2.0.0", diff --git a/src/onboarding/components/Sidebar.js b/src/onboarding/components/Sidebar.js index 5b92dd50f..fea89a6ff 100644 --- a/src/onboarding/components/Sidebar.js +++ b/src/onboarding/components/Sidebar.js @@ -24,11 +24,13 @@ const STEP_DATA = { 'site_info': { title: __( 'Add your site\'s info', 'otter-blocks' ), description: __( 'Add your site title and a logo. No logo yet? No worries, you can add one later.', 'otter-blocks' ), + hideSkip: true, controls: SiteInfo }, 'appearance': { title: __( 'Edit site\'s appearance', 'otter-blocks' ), description: __( 'Change the appearance of your entire site in minutes, by choosing a theme style preset.', 'otter-blocks' ), + hideSkip: true, controls: Appearance }, 'blog_template': { @@ -80,7 +82,7 @@ const Sidebar = ({ isEditorLoading }) => {
{ 1 !== stepIndex ? ( @@ -117,12 +119,14 @@ const Sidebar = ({ isEditorLoading }) => { { __( 'Continue', 'otter-blocks' ) } - + { ! STEP_DATA[ currentStep ]?.hideSkip && ( + + ) }
); diff --git a/src/onboarding/components/steps/Appearance.js b/src/onboarding/components/steps/Appearance.js index 3ca8a59da..a7a5f9bdf 100644 --- a/src/onboarding/components/steps/Appearance.js +++ b/src/onboarding/components/steps/Appearance.js @@ -1,45 +1,108 @@ +/** + * External dependencies + */ +import fastDeepEqual from 'fast-deep-equal/es6'; + +import hash from 'object-hash'; + /** * WordPress dependencies. */ import { __ } from '@wordpress/i18n'; -import { useState } from '@wordpress/element'; +import { + useDispatch, + useSelect +} from '@wordpress/data'; + +import { useMemo } from '@wordpress/element'; /** * Internal dependencies. */ import PalettePreview from '../PalettePreview'; -const { defaultStyles, themeStyles } = window.otterObj; - const Appearance = () => { - const [ palette, setPalette ] = useState( '' ); + const { + globalStyle, + defaultStyles, + themeStyles + } = useSelect( select => { + const { + __experimentalGetCurrentGlobalStylesId, + __experimentalGetCurrentThemeBaseGlobalStyles, + __experimentalGetCurrentThemeGlobalStylesVariations, + getEditedEntityRecord + } = select( 'core' ); + + const globalStylesId = __experimentalGetCurrentGlobalStylesId(); + + return { + globalStyle: getEditedEntityRecord( + 'root', + 'globalStyles', + globalStylesId + ), + defaultStyles: __experimentalGetCurrentThemeBaseGlobalStyles(), + themeStyles: __experimentalGetCurrentThemeGlobalStylesVariations() + }; + }, []); + + const { editEntityRecord } = useDispatch( 'core' ); + + const selectedStyle = useMemo( () => { + if ( ! Object.keys( globalStyle?.styles ).length && ! Object.keys( globalStyle?.settings ).length ) { + return 'default'; + } + + const foundStyle = themeStyles.find( style => + fastDeepEqual( globalStyle?.styles, style?.styles ) && + fastDeepEqual( globalStyle?.settings, style?.settings ) + ); + + return foundStyle ? hash( foundStyle ) : false; + }, [ globalStyle, themeStyles ]); + + const onSelect = ( style ) => { + if ( 'default' === style ) { + editEntityRecord( 'root', 'globalStyles', globalStyle.id, { + styles: {}, + settings: {} + }); + return; + } + + editEntityRecord( 'root', 'globalStyles', globalStyle.id, { + styles: style?.styles, + settings: style?.settings + }); + }; return (
setPalette( 'default' ) } + isSelected={ 'default' === selectedStyle } + backgroundColor={ defaultStyles.settings.color.palette.theme[0].color } + typographyColor={ defaultStyles.settings.color.palette.theme[1].color } + backgroundAltColor={ defaultStyles.settings.color.palette.theme[2].color } + primaryColor={ defaultStyles.settings.color.palette.theme[4].color } + typography={ defaultStyles.styles?.typography } + onSelect={ () => onSelect( 'default' ) } /> { themeStyles.map( ( style, index ) => ( setPalette( index ) } + typography={ style?.styles?.typography || defaultStyles.styles?.typography } + onSelect={ () => onSelect( style ) } /> ) ) }
diff --git a/src/onboarding/components/steps/SiteInfo.js b/src/onboarding/components/steps/SiteInfo.js index 9f24f7c6e..c9eb888c4 100644 --- a/src/onboarding/components/steps/SiteInfo.js +++ b/src/onboarding/components/steps/SiteInfo.js @@ -15,21 +15,26 @@ import { select } from '@wordpress/data'; -import { useState } from '@wordpress/element'; - import { MediaUpload } from '@wordpress/media-utils'; const SiteInfo = () => { - const [ siteTitle, setSiteTitle ] = useState( '' ); - const [ siteLogo, setSiteLogo ] = useState({}); - - const { title } = useSelect( select => { - const { getEditedEntityRecord } = select( 'core' ); + const { + title, + siteLogo, + siteLogoURL + } = useSelect( select => { + const { + getEditedEntityRecord, + getMedia + } = select( 'core' ); const settings = getEditedEntityRecord( 'root', 'site' ); + const siteLogoURL = settings?.site_logo ? getMedia( settings?.site_logo, { context: 'view' }) : null; return { - title: settings?.title + title: settings?.title, + siteLogo: settings?.site_logo, + siteLogoURL: siteLogoURL?.source_url }; }, []); @@ -82,16 +87,16 @@ const SiteInfo = () => { 'core/site-logo', { attributes: { - ...siteTitleBlock.attributes, - url: newLogo.url, - id: newLogo.id + ...siteTitleBlock.attributes } } ) ); } } - setSiteLogo( newLogo ); + editEntityRecord( 'root', 'site', undefined, { + 'site_logo': newLogo?.id + }); }; const removeLogo = () => { @@ -110,7 +115,9 @@ const SiteInfo = () => { ) ); } - setSiteLogo({}); + editEntityRecord( 'root', 'site', undefined, { + 'site_logo': null + }); }; return ( @@ -128,10 +135,10 @@ const SiteInfo = () => { ( <> - { ! siteLogo?.id && ( + { ! siteLogo && (
onOpenMedia( open ) } @@ -140,14 +147,14 @@ const SiteInfo = () => {
) } - { siteLogo?.id && ( + { siteLogo && ( <>
onOpenMedia( open ) } > { diff --git a/src/onboarding/index.js b/src/onboarding/index.js index f03989c0c..26ee5a804 100644 --- a/src/onboarding/index.js +++ b/src/onboarding/index.js @@ -10,12 +10,9 @@ import { registerPlugin } from '@wordpress/plugins'; */ import './style.scss'; import './store'; -import { generateStylesheet } from './utils'; import App from './components/App'; const Render = () => { - generateStylesheet(); - return createPortal( , document.body diff --git a/src/onboarding/utils.js b/src/onboarding/utils.js deleted file mode 100644 index e93824685..000000000 --- a/src/onboarding/utils.js +++ /dev/null @@ -1,18 +0,0 @@ -const generateFontVariables = () => { - const { fontFamilies } = window.otterObj.defaultStyles.settings.settings.typography; - - let fontVariables = []; - - fontFamilies.forEach( ( font, index ) => { - fontVariables.push( `--wp--preset--font-family--${ font.slug }: ${ font.fontFamily };` ); - }); - - return fontVariables; -}; - -export const generateStylesheet = () => { - const fontVariables = generateFontVariables(); - const style = document.createElement( 'style' ); - style.innerHTML = `.o-onboarding { ${ fontVariables.join( '' ) } }`; - document.head.appendChild( style ); -}; From 7815267bf0e2f47951c088e774dff71267e95c66 Mon Sep 17 00:00:00 2001 From: Hardeep Asrani Date: Thu, 23 Nov 2023 10:22:19 +0530 Subject: [PATCH 04/37] Add Saving Mechanism --- src/onboarding/components/Main.js | 2 - src/onboarding/components/Sidebar.js | 21 +++++--- src/onboarding/components/steps/SiteInfo.js | 59 +++++++++++++++------ src/onboarding/store.js | 44 ++++++++++++++- 4 files changed, 99 insertions(+), 27 deletions(-) diff --git a/src/onboarding/components/Main.js b/src/onboarding/components/Main.js index 00986c671..d137ec96b 100644 --- a/src/onboarding/components/Main.js +++ b/src/onboarding/components/Main.js @@ -10,8 +10,6 @@ import { useSelect } from '@wordpress/data'; */ import Homepage from './steps/Homepage'; -import { useIsSiteEditorLoading } from '../hooks'; - const STEP_DATA = { 'site_info': { controls: Homepage diff --git a/src/onboarding/components/Sidebar.js b/src/onboarding/components/Sidebar.js index fea89a6ff..5118135cf 100644 --- a/src/onboarding/components/Sidebar.js +++ b/src/onboarding/components/Sidebar.js @@ -50,17 +50,26 @@ const STEP_DATA = { const Sidebar = ({ isEditorLoading }) => { const { currentStep, - stepIndex + stepIndex, + isSaving } = useSelect( select => { - const { getStep } = select( 'otter/onboarding' ); + const { + getStep, + isSaving + } = select( 'otter/onboarding' ); return { currentStep: getStep()?.id, - stepIndex: getStep()?.value + stepIndex: getStep()?.value, + isSaving: isSaving() }; }); - const { nextStep, previousStep } = useDispatch( 'otter/onboarding' ); + const { + nextStep, + previousStep, + onContinue + } = useDispatch( 'otter/onboarding' ); const Controls = STEP_DATA[ currentStep ]?.controls || null; @@ -71,7 +80,7 @@ const Sidebar = ({ isEditorLoading }) => { return (
- { ( isEditorLoading ) && ( + { ( isEditorLoading || isSaving ) && (
@@ -114,7 +123,7 @@ const Sidebar = ({ isEditorLoading }) => {
diff --git a/src/onboarding/components/steps/SiteInfo.js b/src/onboarding/components/steps/SiteInfo.js index c9eb888c4..f047823e0 100644 --- a/src/onboarding/components/steps/SiteInfo.js +++ b/src/onboarding/components/steps/SiteInfo.js @@ -21,20 +21,25 @@ const SiteInfo = () => { const { title, siteLogo, - siteLogoURL + siteLogoURL, + templateParts } = useSelect( select => { const { getEditedEntityRecord, getMedia } = select( 'core' ); + const { getCurrentTemplateTemplateParts } = select( 'core/edit-site' ); + const settings = getEditedEntityRecord( 'root', 'site' ); const siteLogoURL = settings?.site_logo ? getMedia( settings?.site_logo, { context: 'view' }) : null; + const templateParts = getCurrentTemplateTemplateParts(); return { title: settings?.title, siteLogo: settings?.site_logo, - siteLogoURL: siteLogoURL?.source_url + siteLogoURL: siteLogoURL?.source_url, + templateParts }; }, []); @@ -69,7 +74,13 @@ const SiteInfo = () => { }; const onOpenMedia = open => { - setEditedEntity( 'wp_template_part', 'raft//header', 'edit' ); + const template = templateParts.find( part => 'header' === part.templatePart.slug )?.templatePart.id ?? templateParts.find( part => part.templatePart.slug?.includes( 'header' ) )?.templatePartid; + const editedEntity = select( 'core/edit-site' ).getEditedPostId(); + + if ( template && template !== editedEntity ) { + setEditedEntity( 'wp_template_part', template, 'edit' ); + } + open(); }; @@ -100,24 +111,38 @@ const SiteInfo = () => { }; const removeLogo = () => { - const blocks = select( 'core/block-editor' ).getBlocks(); + const template = templateParts.find( part => 'header' === part.templatePart.slug )?.templatePart.id ?? templateParts.find( part => part.templatePart.slug?.includes( 'header' ) )?.templatePartid; + const editedEntity = select( 'core/edit-site' ).getEditedPostId(); - let siteLogoBlock = findBlock( blocks, 'core/site-logo' ); + if ( template && template !== editedEntity ) { + setEditedEntity( 'wp_template_part', template, 'edit' ); + } + + setTimeout( () => { + if ( template && template !== editedEntity ) { + removeLogo(); + return; + } + + const blocks = select( 'core/block-editor' ).getBlocks(); - if ( siteLogoBlock ) { - replaceBlock( siteLogoBlock.clientId, wp.blocks.createBlock( - 'core/site-title', - { - attributes: { - ...siteLogoBlock.attributes + let siteLogoBlock = findBlock( blocks, 'core/site-logo' ); + + if ( siteLogoBlock ) { + replaceBlock( siteLogoBlock.clientId, wp.blocks.createBlock( + 'core/site-title', + { + attributes: { + ...siteLogoBlock.attributes + } } - } - ) ); - } + ) ); + } - editEntityRecord( 'root', 'site', undefined, { - 'site_logo': null - }); + editEntityRecord( 'root', 'site', undefined, { + 'site_logo': null + }); + }, 1000 ); }; return ( diff --git a/src/onboarding/store.js b/src/onboarding/store.js index b6cdf921c..8de47a2aa 100644 --- a/src/onboarding/store.js +++ b/src/onboarding/store.js @@ -5,11 +5,17 @@ import apiFetch from '@wordpress/api-fetch'; import { createReduxStore, - register + dispatch, + register, + select } from '@wordpress/data'; import { addQueryArgs } from '@wordpress/url'; +const { __experimentalGetDirtyEntityRecords } = select( 'core' ); + +const { saveEditedEntityRecord } = dispatch( 'core' ); + const STEPS = [ { id: 'site_info', @@ -36,7 +42,8 @@ const STEPS = [ const DEFAULT_STATE = { step: 1, templates: {}, - templateParts: {} + templateParts: {}, + isSaving: false }; const actions = { @@ -62,6 +69,25 @@ const actions = { dispatch( actions.setStep( newStep ) ); }; }, + onContinue() { + return async({ dispatch, select }) => { + const step = select.getStep(); + + if ([ 'site_info', 'appearance' ].includes( step.id ) ) { + const edits = __experimentalGetDirtyEntityRecords(); + + dispatch( actions.setSaving( true ) ); + + await Promise.all( edits.map( async edit => { + await saveEditedEntityRecord( edit.kind, edit.name, edit?.key ); + }) ); + + dispatch( actions.setSaving( false ) ); + } + + dispatch( actions.nextStep() ); + }; + }, setTemplate( template ) { return { type: 'SET_TEMPLATE', @@ -74,6 +100,12 @@ const actions = { templatePart }; }, + setSaving( isSaving ) { + return { + type: 'SET_SAVING', + isSaving + }; + }, fetchFromAPI( path ) { return { type: 'FETCH_FROM_API', @@ -108,6 +140,11 @@ const store = createReduxStore( 'otter/onboarding', { [action.templatePart.id]: action.templatePart } }; + case 'SET_SAVING': + return { + ...state, + isSaving: action.isSaving + }; } return state; @@ -124,6 +161,9 @@ const store = createReduxStore( 'otter/onboarding', { }, getTemplatePart( state, slug ) { return state.templateParts?.[ slug ]; + }, + isSaving( state ) { + return state.isSaving; } }, From 3f0c16162cd6bedaa712c4034ad1df21b4595e1c Mon Sep 17 00:00:00 2001 From: Hardeep Asrani Date: Fri, 24 Nov 2023 19:36:27 +0530 Subject: [PATCH 05/37] Add Archive Step & Saving --- inc/class-main.php | 1 + inc/server/class-fse-onboarding-server.php | 181 ++++++++++++++++++ .../blocks/advanced-heading/controls.js | 2 +- .../blocks/advanced-heading/editor.scss | 2 +- src/onboarding/components/Main.js | 5 +- src/onboarding/components/Sidebar.js | 2 +- src/onboarding/components/TemplateSelector.js | 62 ++++++ src/onboarding/components/steps/Appearance.js | 2 +- src/onboarding/components/steps/Archive.js | 88 +++++++++ src/onboarding/store.js | 122 +++++++++++- src/onboarding/style.scss | 47 +++++ 11 files changed, 498 insertions(+), 16 deletions(-) create mode 100644 inc/server/class-fse-onboarding-server.php create mode 100644 src/onboarding/components/TemplateSelector.js create mode 100644 src/onboarding/components/steps/Archive.js diff --git a/inc/class-main.php b/inc/class-main.php index fe0ba4de3..57a8a2720 100644 --- a/inc/class-main.php +++ b/inc/class-main.php @@ -74,6 +74,7 @@ public function autoload_classes() { '\ThemeIsle\GutenbergBlocks\Server\Dashboard_Server', '\ThemeIsle\GutenbergBlocks\Server\Dynamic_Content_Server', '\ThemeIsle\GutenbergBlocks\Server\Stripe_Server', + '\ThemeIsle\GutenbergBlocks\Server\FSE_Onboarding_Server', '\ThemeIsle\GutenbergBlocks\Integration\Form_Providers', '\ThemeIsle\GutenbergBlocks\Integration\Form_Email', '\ThemeIsle\GutenbergBlocks\Server\Form_Server', diff --git a/inc/server/class-fse-onboarding-server.php b/inc/server/class-fse-onboarding-server.php new file mode 100644 index 000000000..932caea23 --- /dev/null +++ b/inc/server/class-fse-onboarding-server.php @@ -0,0 +1,181 @@ +namespace . $this->version; + + register_rest_route( + $namespace, + '/onboarding/templates', + array( + array( + 'methods' => \WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_templates' ), + 'permission_callback' => function () { + return current_user_can( 'manage_options' ); + }, + ), + ) + ); + } + + /** + * List Templates. + * + * @param \WP_REST_Request $request The request. + * + * @return \WP_REST_Response + * @access public + */ + public function get_templates( \WP_REST_Request $request ) { + $support = get_theme_support( 'otter-onboarding' ); + + if ( false === $support && ! is_array( $support ) || ! isset( $support[0]['templates'] ) ) { + return rest_ensure_response( + array( + 'success' => false, + 'data' => array( + 'message' => __( 'Missing support', 'otter-blocks' ), + ), + ) + ); + } + + $templates = $support[0]['templates']; + + if ( ! $templates ) { + return rest_ensure_response( + array( + 'success' => false, + 'data' => array( + 'message' => __( 'Missing templates', 'otter-blocks' ), + ), + ) + ); + } + + foreach ( $templates as $key => $categories ) { + foreach ( $categories as $i => $template ) { + if ( file_exists( $template['file'] ) ) { + $templates[ $key ][ $i ]['content']['raw'] = file_get_contents( $template['file'] ); + unset( $templates[ $key ][ $i ]['file'] ); + } else { + unset( $templates[ $key ][ $i ] ); + } + } + } + + return rest_ensure_response( + array( + 'success' => true, + 'data' => $templates, + ) + ); + } + + /** + * Get Product Pricing. + * + * @param \WP_REST_Request $request The request. + * + * @return \WP_REST_Response + * @access public + */ + public function get_price( \WP_REST_Request $request ) { + return ( new Stripe_API() )->create_request( + 'prices', + array( + 'active' => true, + 'product' => $request->get_param( 'id' ), + 'limit' => 50, + ) + ); + } + + /** + * The instance method for the static class. + * Defines and returns the instance of the static class. + * + * @static + * @since 1.7.0 + * @access public + * @return FSE_Onboarding_Server + */ + public static function instance() { + if ( is_null( self::$instance ) ) { + self::$instance = new self(); + self::$instance->init(); + } + + return self::$instance; + } + + /** + * Throw error on object clone + * + * The whole idea of the singleton design pattern is that there is a single + * object therefore, we don't want the object to be cloned. + * + * @access public + * @since 1.7.0 + * @return void + */ + public function __clone() { + // Cloning instances of the class is forbidden. + _doing_it_wrong( __FUNCTION__, esc_html__( 'Cheatin’ huh?', 'otter-blocks' ), '1.0.0' ); + } + + /** + * Disable unserializing of the class + * + * @access public + * @since 1.7.0 + * @return void + */ + public function __wakeup() { + // Unserializing instances of the class is forbidden. + _doing_it_wrong( __FUNCTION__, esc_html__( 'Cheatin’ huh?', 'otter-blocks' ), '1.0.0' ); + } +} diff --git a/src/blocks/blocks/advanced-heading/controls.js b/src/blocks/blocks/advanced-heading/controls.js index b07c6eca7..cf05e0c3f 100644 --- a/src/blocks/blocks/advanced-heading/controls.js +++ b/src/blocks/blocks/advanced-heading/controls.js @@ -108,7 +108,7 @@ const Controls = ({ ( +
+
+ ); +}; + +export default Finish; diff --git a/src/onboarding/components/Sidebar.js b/src/onboarding/components/Sidebar.js index 466e5cdf9..e4898593e 100644 --- a/src/onboarding/components/Sidebar.js +++ b/src/onboarding/components/Sidebar.js @@ -51,17 +51,23 @@ const Sidebar = ({ isEditorLoading }) => { const { currentStep, stepIndex, - isSaving + isSaving, + siteURL } = useSelect( select => { + const { getSite } = select( 'core' ); + const { getStep, isSaving } = select( 'otter/onboarding' ); + const siteURL = getSite()?.url; + return { currentStep: getStep()?.id, stepIndex: getStep()?.value, - isSaving: isSaving() + isSaving: isSaving(), + siteURL }; }); @@ -74,8 +80,7 @@ const Sidebar = ({ isEditorLoading }) => { const Controls = STEP_DATA[ currentStep ]?.controls || null; const onExit = () => { - const node = document.getElementById( 'otter-onboarding' ); - node.remove(); + window.open( siteURL, '_self' ); }; return ( diff --git a/src/onboarding/components/steps/Pages.js b/src/onboarding/components/steps/Pages.js index 0d11b516e..a9b853b74 100644 --- a/src/onboarding/components/steps/Pages.js +++ b/src/onboarding/components/steps/Pages.js @@ -92,7 +92,7 @@ const Pages = () => { const { setSelectedTemplate } = useDispatch( 'otter/onboarding' ); - const onSelect = ( value ) => { + const onSelect = value => { const isSelected = selectedTemplates.includes( value ); if ( isSelected ) { diff --git a/src/onboarding/store.js b/src/onboarding/store.js index 58080631f..4aeed73cf 100644 --- a/src/onboarding/store.js +++ b/src/onboarding/store.js @@ -16,7 +16,8 @@ const { __experimentalGetDirtyEntityRecords } = select( 'core' ); const { editEntityRecord, - saveEditedEntityRecord + saveEditedEntityRecord, + saveEntityRecord } = dispatch( 'core' ); const STEPS = [ @@ -53,7 +54,9 @@ const DEFAULT_STATE = { single: '', pageTemplates: [] }, - isSaving: false + importedTemplates: [], + isSaving: false, + isFinished: false }; const actions = { @@ -66,10 +69,15 @@ const actions = { nextStep() { return ({ dispatch, select }) => { const step = select.getStep(); - const newStep = STEPS.length < ( step.value + 1 ) ? STEPS.length : ( step.value + 1 ); + const isLast = STEPS.length < ( step.value + 1 ); + const newStep = isLast ? STEPS.length : ( step.value + 1 ); dispatch( actions.setSaving( false ) ); dispatch( actions.setStep( newStep ) ); + + if ( isLast ) { + dispatch( actions.setFinished( true ) ); + } }; }, previousStep() { @@ -81,6 +89,12 @@ const actions = { dispatch( actions.setStep( newStep ) ); }; }, + setFinished( isFinished ) { + return { + type: 'SET_FINISHED', + isFinished + }; + }, onContinue() { return async({ dispatch, select }) => { const step = select.getStep(); @@ -121,6 +135,28 @@ const actions = { }) ); } + if ( 'additional_templates' === step.id ) { + const selectedTemplates = select.getSelectedTemplate( 'pageTemplates' ); + const pageTemplates = select.getLibrary( 'page_templates' ); + const importedTemplates = select.getImportedTemplates(); // It will be array similar to selectedTemplates + + await Promise.all( + selectedTemplates + .filter( template => ! importedTemplates.find( importedTemplate => importedTemplate === template ) ) + .map( async template => { + const pageTemplate = pageTemplates[ template ]; + + await saveEntityRecord( 'postType', 'page', { + status: 'publish', + title: pageTemplate.title, + content: pageTemplate.content.raw, + template: pageTemplate.template + }); + + dispatch( actions.setImportedTemplate( template ) ); + }) ); + } + dispatch( actions.setSaving( false ) ); dispatch( actions.nextStep() ); }; @@ -156,6 +192,12 @@ const actions = { template }; }, + setImportedTemplate( template ) { + return { + type: 'SET_IMPORTED_TEMPLATE', + template + }; + }, setSaving( isSaving ) { return { type: 'SET_SAVING', @@ -178,6 +220,11 @@ const store = createReduxStore( 'otter/onboarding', { ...state, step: action.step }; + case 'SET_FINISHED': + return { + ...state, + isFinished: action.isFinished + }; case 'SET_TEMPLATE': return { ...state, @@ -215,6 +262,14 @@ const store = createReduxStore( 'otter/onboarding', { [action.slug]: action.template } }; + case 'SET_IMPORTED_TEMPLATE': + return { + ...state, + importedTemplates: [ + ...state.importedTemplates, + action.template + ] + }; case 'SET_SAVING': return { ...state, @@ -250,8 +305,14 @@ const store = createReduxStore( 'otter/onboarding', { getSelectedTemplate( state, type ) { return state.selectedTemplates[ type ]; }, + getImportedTemplates( state ) { + return state.importedTemplates; + }, isSaving( state ) { return state.isSaving; + }, + isFinished( state ) { + return state.isFinished; } }, diff --git a/src/onboarding/style.scss b/src/onboarding/style.scss index 72e03c697..91faf3b90 100644 --- a/src/onboarding/style.scss +++ b/src/onboarding/style.scss @@ -4,6 +4,18 @@ $main-color: #151414; $border-color: #E5E5E5; $primary: #0366D5; +@mixin primary-button-styles { + padding: 16px 32px; + justify-content: center; + align-items: center; + gap: 32px; + border-radius: 5px; + font-size: 18px; + font-style: normal; + font-weight: 700; + height: auto; +} + div.error, div.notice { display: none; } @@ -28,6 +40,51 @@ div.error, div.notice { --wp-components-color-accent: #0366D5; } +.o-finish { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + + &__container { + background: #fff; + border-radius: 32px; + padding: 5rem; + max-width: 980px; + + h1 { + font-size: 40px; + font-style: normal; + font-weight: 700; + line-height: 1; + } + + p { + font-size: 18px; + font-style: normal; + font-weight: 400; + } + + .components-textarea-control__input { + border-radius: 6px; + border: 1px solid $border-color; + background: #FFF; + padding: 1rem; + resize: none; + } + + .components-button.is-primary { + @include primary-button-styles; + margin-top: 1rem; + } + } + + &__logo { + width: 120px; + height: 120px; + } +} + .o-sidebar { background: #fff; box-shadow: 0 0 10px rgba(0,0,0,.1); @@ -124,22 +181,14 @@ div.error, div.notice { padding: 2rem; .components-button.is-primary { - padding: 16px 32px; - justify-content: center; - align-items: center; - gap: 32px; - border-radius: 5px; + @include primary-button-styles; width: 100%; - font-size: 18px; - font-style: normal; - font-weight: 700; - height: auto; } .components-button.is-tertiary { font-size: 14px; font-style: normal; - font-weight: 400; + font-weight: 400; --wp-components-color-accent: $secondary-text; } } From be60d785c440b3f2700a3b74830edfccc0e1c214 Mon Sep 17 00:00:00 2001 From: Hardeep Asrani Date: Mon, 27 Nov 2023 16:05:18 +0530 Subject: [PATCH 09/37] Add Steps Condition --- inc/plugins/class-fse-onboarding.php | 56 +++++++++++++++++- inc/server/class-fse-onboarding-server.php | 32 ++--------- src/onboarding/components/Main.js | 32 +---------- src/onboarding/components/Sidebar.js | 32 +---------- src/onboarding/steps.js | 66 ++++++++++++++++++++++ src/onboarding/store.js | 46 +++++++-------- 6 files changed, 147 insertions(+), 117 deletions(-) create mode 100644 src/onboarding/steps.js diff --git a/inc/plugins/class-fse-onboarding.php b/inc/plugins/class-fse-onboarding.php index 80732cd1b..e89d8e647 100644 --- a/inc/plugins/class-fse-onboarding.php +++ b/inc/plugins/class-fse-onboarding.php @@ -28,6 +28,57 @@ public function init() { add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_options_assets' ) ); } + /** + * Get Theme Templates + */ + public function get_templates() { + $support = get_theme_support( 'otter-onboarding' ); + + if ( false === $support && ! is_array( $support ) || ( ! isset( $support[0]['templates'] ) && ! isset( $support[0]['page_templates'] ) ) ) { + return false; + } + + $template = array(); + + if ( isset( $support[0]['templates'] ) ) { + $templates = $support[0]['templates']; + } + + if ( isset( $support[0]['page_templates'] ) ) { + $templates['page_templates'] = $support[0]['page_templates']; + } + + if ( ! $templates ) { + return false; + } + + foreach ( $templates as $key => $categories ) { + foreach ( $categories as $i => $template ) { + if ( file_exists( $template['file'] ) ) { + $templates[ $key ][ $i ]['content']['raw'] = file_get_contents( $template['file'] ); + unset( $templates[ $key ][ $i ]['file'] ); + } else { + unset( $templates[ $key ][ $i ] ); + } + } + } + + return $templates; + } + + /** + * Get Templates Types + */ + public function get_templates_types() { + $templates = $this->get_templates(); + + if ( ! $templates ) { + return array(); + } + + return array_keys( $templates ); + } + /** * Enqueue options assets. */ @@ -59,8 +110,9 @@ public function enqueue_options_assets() { apply_filters( 'otter_onboarding_data', array( - 'version' => OTTER_BLOCKS_VERSION, - 'assetsPath' => OTTER_BLOCKS_URL . 'assets/', + 'version' => OTTER_BLOCKS_VERSION, + 'assetsPath' => OTTER_BLOCKS_URL . 'assets/', + 'supportedSteps' => $this->get_templates_types(), ) ) ); diff --git a/inc/server/class-fse-onboarding-server.php b/inc/server/class-fse-onboarding-server.php index 4162f1d55..b99539aec 100644 --- a/inc/server/class-fse-onboarding-server.php +++ b/inc/server/class-fse-onboarding-server.php @@ -7,6 +7,8 @@ namespace ThemeIsle\GutenbergBlocks\Server; +use ThemeIsle\GutenbergBlocks\Plugins\FSE_Onboarding; + /** * Class FSE_Onboarding_Server */ @@ -70,24 +72,9 @@ public function register_routes() { * @access public */ public function get_templates( \WP_REST_Request $request ) { - $support = get_theme_support( 'otter-onboarding' ); - - if ( false === $support && ! is_array( $support ) || ( ! isset( $support[0]['templates'] ) && ! isset( $support[0]['page_templates'] ) ) ) { - return rest_ensure_response( - array( - 'success' => false, - 'data' => array( - 'message' => __( 'Missing support', 'otter-blocks' ), - ), - ) - ); - } + $fse_onboarding = FSE_Onboarding::instance(); - $templates = $support[0]['templates']; - - if ( $support[0]['page_templates'] ) { - $templates['page_templates'] = $support[0]['page_templates']; - } + $templates = $fse_onboarding->get_templates(); if ( ! $templates ) { return rest_ensure_response( @@ -100,17 +87,6 @@ public function get_templates( \WP_REST_Request $request ) { ); } - foreach ( $templates as $key => $categories ) { - foreach ( $categories as $i => $template ) { - if ( file_exists( $template['file'] ) ) { - $templates[ $key ][ $i ]['content']['raw'] = file_get_contents( $template['file'] ); - unset( $templates[ $key ][ $i ]['file'] ); - } else { - unset( $templates[ $key ][ $i ] ); - } - } - } - return rest_ensure_response( array( 'success' => true, diff --git a/src/onboarding/components/Main.js b/src/onboarding/components/Main.js index 0f1781601..46fd9c81e 100644 --- a/src/onboarding/components/Main.js +++ b/src/onboarding/components/Main.js @@ -10,35 +10,7 @@ import { useSelect } from '@wordpress/data'; /** * Internal dependencies. */ -import Homepage from './steps/Homepage'; -import Template from './steps/Template'; -import Pages from './steps/Pages'; - -const STEP_DATA = { - 'site_info': { - controls: Homepage - }, - 'appearance': { - controls: Homepage - }, - 'archive_template': { - controls: Template, - props: { - type: 'archive', - label: __( 'Post Archive', 'otter-blocks' ) - } - }, - 'single_template': { - controls: Template, - props: { - type: 'single', - label: __( 'Single Post', 'otter-blocks' ) - } - }, - 'additional_templates': { - controls: Pages - } -}; +import STEP_DATA from '../steps'; const Main = ({ isEditorLoading }) => { const { currentStep } = useSelect( select => { @@ -49,7 +21,7 @@ const Main = ({ isEditorLoading }) => { }; }); - const Controls = STEP_DATA[ currentStep ]?.controls || null; + const Controls = STEP_DATA[ currentStep ]?.content || null; if ( isEditorLoading ) { return ( diff --git a/src/onboarding/components/Sidebar.js b/src/onboarding/components/Sidebar.js index e4898593e..6ec20bbd1 100644 --- a/src/onboarding/components/Sidebar.js +++ b/src/onboarding/components/Sidebar.js @@ -17,35 +17,7 @@ import { /** * Internal dependencies. */ -import SiteInfo from './steps/SiteInfo'; -import Appearance from './steps/Appearance'; - -const STEP_DATA = { - 'site_info': { - title: __( 'Add your site\'s info', 'otter-blocks' ), - description: __( 'Add your site title and a logo. No logo yet? No worries, you can add one later.', 'otter-blocks' ), - hideSkip: true, - controls: SiteInfo - }, - 'appearance': { - title: __( 'Edit site\'s appearance', 'otter-blocks' ), - description: __( 'Change the appearance of your entire site in minutes, by choosing a theme style preset.', 'otter-blocks' ), - hideSkip: true, - controls: Appearance - }, - 'archive_template': { - title: __( 'Select a template for your Blog Page', 'otter-blocks' ), - description: __( 'Choose a layout for for how your blog posts appear in the blog page.', 'otter-blocks' ) - }, - 'single_template': { - title: __( 'Select a template for your Single Posts', 'otter-blocks' ), - description: __( 'Choose a layout for your single posts', 'otter-blocks' ) - }, - 'additional_templates': { - title: __( 'Add additional pages to your site', 'otter-blocks' ), - description: __( 'Create additional pages to your website.', 'otter-blocks' ) - } -}; +import STEP_DATA from '../steps'; const Sidebar = ({ isEditorLoading }) => { const { @@ -99,7 +71,7 @@ const Sidebar = ({ isEditorLoading }) => { src={ `${ window.otterObj.assetsPath }images/logo-alt.png` } /> - { 1 !== stepIndex ? ( + { 0 !== stepIndex ? (
); diff --git a/src/onboarding/components/Sidebar.js b/src/onboarding/components/Sidebar.js index 6ec20bbd1..1fc4c0975 100644 --- a/src/onboarding/components/Sidebar.js +++ b/src/onboarding/components/Sidebar.js @@ -18,13 +18,15 @@ import { * Internal dependencies. */ import STEP_DATA from '../steps'; +import Main from './Main'; const Sidebar = ({ isEditorLoading }) => { const { currentStep, stepIndex, isSaving, - siteURL + siteURL, + isSmall } = useSelect( select => { const { getSite } = select( 'core' ); @@ -33,13 +35,18 @@ const Sidebar = ({ isEditorLoading }) => { isSaving } = select( 'otter/onboarding' ); + const { isViewportMatch } = select( 'core/viewport' ); + + const isSmall = isViewportMatch( '< medium' ); + const siteURL = getSite()?.url; return { currentStep: getStep()?.id, stepIndex: getStep()?.value, isSaving: isSaving(), - siteURL + siteURL, + isSmall }; }); @@ -94,6 +101,12 @@ const Sidebar = ({ isEditorLoading }) => {

{ STEP_DATA[ currentStep ]?.description }

+ { isSmall && ( +
+ ) } + { Controls && }
diff --git a/src/onboarding/style.scss b/src/onboarding/style.scss index 91faf3b90..b79ebd3f1 100644 --- a/src/onboarding/style.scss +++ b/src/onboarding/style.scss @@ -38,6 +38,18 @@ div.error, div.notice { height: 100vh; font-family: Arial,sans-serif; --wp-components-color-accent: #0366D5; + + @media ( max-width: 600px ) { + display: block; + } + + @media ( min-width: 600px ) and ( max-width: 1023px ) { + grid-template-columns: 2fr 3fr; + } + + @media ( min-width: 1920px ) { + grid-template-columns: 1fr 4fr; + } } .o-finish { @@ -92,6 +104,10 @@ div.error, div.notice { flex-direction: column; overflow: scroll; + @media ( max-width: 600px ) { + height: 100vh; + } + &__loader { height: 100vh; position: absolute; @@ -101,6 +117,18 @@ div.error, div.notice { width: 25vw; background: rgba(255, 255, 255, 0.5); z-index: 9; + + @media ( max-width: 600px ) { + width: 100vw; + } + + @media ( min-width: 600px ) and ( max-width: 1023px ) { + width: 40vw; + } + + @media ( min-width: 1920px ) { + width: 25vw; + } } &__header { @@ -274,7 +302,7 @@ div.error, div.notice { } &__typography { - font-size: 2.6vw; + font-size: clamp( 2.5rem, 2.6vw, 3rem ); font-style: normal; font-weight: 700; line-height: 150%; @@ -295,6 +323,13 @@ div.error, div.notice { } } } + + .o-main { + background: transparent; + height: auto; + margin-top: -2rem; + margin-bottom: 2rem; + } } .o-main { @@ -318,6 +353,14 @@ div.error, div.notice { padding: 4rem 6rem; gap: 2rem; + @media ( max-width: 600px ) { + grid-template-columns: 1fr; + } + + @media ( min-width: 1920px ) { + grid-template-columns: 1fr 1fr 1fr; + } + &__item { cursor: pointer; position: relative; From 4149e1b25b260d14afdeef771a9b5a61035cba30 Mon Sep 17 00:00:00 2001 From: Hardeep Asrani Date: Mon, 27 Nov 2023 21:41:06 +0530 Subject: [PATCH 13/37] Improve Homepage Preview --- src/onboarding/components/steps/Homepage.js | 1 + src/onboarding/style.scss | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/onboarding/components/steps/Homepage.js b/src/onboarding/components/steps/Homepage.js index 3679c2154..f2b2f3291 100644 --- a/src/onboarding/components/steps/Homepage.js +++ b/src/onboarding/components/steps/Homepage.js @@ -21,6 +21,7 @@ const Homepage = () => { return ( ); diff --git a/src/onboarding/style.scss b/src/onboarding/style.scss index b79ebd3f1..2be25ed16 100644 --- a/src/onboarding/style.scss +++ b/src/onboarding/style.scss @@ -336,6 +336,14 @@ div.error, div.notice { height: 100vh; background: $main-bg; overflow: scroll; + + .block-editor-block-preview__container { + .block-editor-block-preview__content, + iframe { + height: 100% !important; + max-height: none !important; + } + } } .o-preview { From 734a953c50b81c3302dad45f54c4bd351f20c7da Mon Sep 17 00:00:00 2001 From: Hardeep Asrani Date: Tue, 28 Nov 2023 15:51:27 +0530 Subject: [PATCH 14/37] Add Redirection Conditions --- inc/plugins/class-fse-onboarding.php | 156 +++++++++++++++++---- inc/server/class-fse-onboarding-server.php | 41 ++---- src/onboarding/components/Finish.js | 4 +- src/onboarding/components/Sidebar.js | 2 +- src/onboarding/index.js | 14 +- src/onboarding/steps.js | 2 +- src/onboarding/style.scss | 2 +- 7 files changed, 158 insertions(+), 63 deletions(-) diff --git a/inc/plugins/class-fse-onboarding.php b/inc/plugins/class-fse-onboarding.php index e89d8e647..eb634ce06 100644 --- a/inc/plugins/class-fse-onboarding.php +++ b/inc/plugins/class-fse-onboarding.php @@ -25,20 +25,108 @@ class FSE_Onboarding { * Initialize the class */ public function init() { + add_action( 'after_switch_theme', array( $this, 'on_switch_theme' ) ); add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_options_assets' ) ); + add_action( 'admin_menu', array( $this, 'register_menu_page' ) ); + } + + /** + * Register menu page + * + * @access public + * @return void + */ + public function register_menu_page() { + $has_support = get_theme_support( 'otter-onboarding' ); + + if ( false === $has_support || ! current_user_can( 'manage_options' ) ) { + return; + } + + add_submenu_page( + 'otter', + esc_html__( 'FSE Onboarding', 'otter-blocks' ), + esc_html__( 'FSE Onboarding', 'otter-blocks' ), + 'manage_options', + 'otter-onboarding', + function() { + $redirect = add_query_arg( + array( + 'onboarding' => 'true', + ), + admin_url( 'site-editor.php' ) + ); + + echo '

Redirecting...

+ '; + } + ); + } + + /** + * On switch theme + * + * @access public + * @return void + */ + public function on_switch_theme() { + // Check if the theme has support for FSE. + $support = get_theme_support( 'otter-onboarding' ); + + if ( false === $support ) { + return; + } + + $status = get_option( 'otter_onboarding_status', array() ); + $slug = get_stylesheet(); + + if ( ! empty( $status[ $slug ] ) ) { + return; + } + + // Run the onboarding. + self::run_onboarding(); + } + + /** + * Run the onboarding. + * + * @access public + * @return void + */ + public static function run_onboarding() { + $redirect = add_query_arg( + array( + 'onboarding' => 'true', + ), + admin_url( 'site-editor.php' ) + ); + + // Set the onboarding ran option. + $status = get_option( 'otter_onboarding_status', array() ); + $slug = get_stylesheet(); + $status[ $slug ] = true; + update_option( 'otter_onboarding_status', $status ); + + // Redirect to the onboarding. + wp_safe_redirect( $redirect ); + exit; } /** * Get Theme Templates + * + * @access public + * @return array|false */ public function get_templates() { - $support = get_theme_support( 'otter-onboarding' ); + $support = get_theme_support( 'otter-onboarding' ); - if ( false === $support && ! is_array( $support ) || ( ! isset( $support[0]['templates'] ) && ! isset( $support[0]['page_templates'] ) ) ) { - return false; - } + if ( false === $support || ! is_array( $support ) || ( ! isset( $support[0]['templates'] ) && ! isset( $support[0]['page_templates'] ) ) ) { + return false; + } - $template = array(); + $templates = array(); if ( isset( $support[0]['templates'] ) ) { $templates = $support[0]['templates']; @@ -48,26 +136,29 @@ public function get_templates() { $templates['page_templates'] = $support[0]['page_templates']; } - if ( ! $templates ) { - return false; - } - - foreach ( $templates as $key => $categories ) { - foreach ( $categories as $i => $template ) { - if ( file_exists( $template['file'] ) ) { - $templates[ $key ][ $i ]['content']['raw'] = file_get_contents( $template['file'] ); - unset( $templates[ $key ][ $i ]['file'] ); - } else { - unset( $templates[ $key ][ $i ] ); - } - } - } + if ( ! $templates ) { + return false; + } + + foreach ( $templates as $key => $categories ) { + foreach ( $categories as $i => $template ) { + if ( file_exists( $template['file'] ) ) { + $templates[ $key ][ $i ]['content']['raw'] = file_get_contents( $template['file'] ); + unset( $templates[ $key ][ $i ]['file'] ); + } else { + unset( $templates[ $key ][ $i ] ); + } + } + } return $templates; } /** * Get Templates Types + * + * @access public + * @return array */ public function get_templates_types() { $templates = $this->get_templates(); @@ -79,10 +170,25 @@ public function get_templates_types() { return array_keys( $templates ); } - /** - * Enqueue options assets. - */ - public function enqueue_options_assets() { + /** + * Enqueue options assets. + * + * @access public + * @return void + */ + public function enqueue_options_assets() { + $current_screen = get_current_screen(); + $has_support = get_theme_support( 'otter-onboarding' ); + + if ( + false === $has_support || + ! current_user_can( 'manage_options' ) || + ! isset( $current_screen->id ) || + 'site-editor' !== $current_screen->id + ) { + return; + } + $asset_file = include OTTER_BLOCKS_PATH . '/build/onboarding/index.asset.php'; wp_enqueue_media(); @@ -106,7 +212,7 @@ public function enqueue_options_assets() { wp_localize_script( 'otter-onboarding-scripts', - 'otterObj', + 'otterOnboardingData', apply_filters( 'otter_onboarding_data', array( @@ -116,7 +222,7 @@ public function enqueue_options_assets() { ) ) ); - } + } /** * The instance method for the static class. diff --git a/inc/server/class-fse-onboarding-server.php b/inc/server/class-fse-onboarding-server.php index b99539aec..4c26e45c9 100644 --- a/inc/server/class-fse-onboarding-server.php +++ b/inc/server/class-fse-onboarding-server.php @@ -56,7 +56,7 @@ public function register_routes() { 'methods' => \WP_REST_Server::READABLE, 'callback' => array( $this, 'get_templates' ), 'permission_callback' => function () { - return current_user_can( 'manage_options' ); + return current_user_can( 'manage_options' ); }, ), ) @@ -76,16 +76,16 @@ public function get_templates( \WP_REST_Request $request ) { $templates = $fse_onboarding->get_templates(); - if ( ! $templates ) { - return rest_ensure_response( - array( - 'success' => false, - 'data' => array( - 'message' => __( 'Missing templates', 'otter-blocks' ), - ), - ) - ); - } + if ( ! $templates ) { + return rest_ensure_response( + array( + 'success' => false, + 'data' => array( + 'message' => __( 'Missing templates', 'otter-blocks' ), + ), + ) + ); + } return rest_ensure_response( array( @@ -95,25 +95,6 @@ public function get_templates( \WP_REST_Request $request ) { ); } - /** - * Get Product Pricing. - * - * @param \WP_REST_Request $request The request. - * - * @return \WP_REST_Response - * @access public - */ - public function get_price( \WP_REST_Request $request ) { - return ( new Stripe_API() )->create_request( - 'prices', - array( - 'active' => true, - 'product' => $request->get_param( 'id' ), - 'limit' => 50, - ) - ); - } - /** * The instance method for the static class. * Defines and returns the instance of the static class. diff --git a/src/onboarding/components/Finish.js b/src/onboarding/components/Finish.js index c32b24ad3..a0e9e9403 100644 --- a/src/onboarding/components/Finish.js +++ b/src/onboarding/components/Finish.js @@ -12,7 +12,7 @@ import { useSelect } from '@wordpress/data'; import { useState } from '@wordpress/element'; -const { version } = window.otterObj; +const { version } = window.otterOnboardingData; const Finish = () => { const [ feedback, setFeedback ] = useState( '' ); @@ -74,7 +74,7 @@ const Finish = () => {

{ __( 'Your website is ready to go!', 'otter-blocks' ) }

diff --git a/src/onboarding/components/Sidebar.js b/src/onboarding/components/Sidebar.js index 1fc4c0975..ad886558c 100644 --- a/src/onboarding/components/Sidebar.js +++ b/src/onboarding/components/Sidebar.js @@ -75,7 +75,7 @@ const Sidebar = ({ isEditorLoading }) => {
{ 0 !== stepIndex ? ( diff --git a/src/onboarding/index.js b/src/onboarding/index.js index 26ee5a804..dd85fdb17 100644 --- a/src/onboarding/index.js +++ b/src/onboarding/index.js @@ -19,6 +19,14 @@ const Render = () => { ); }; -registerPlugin( 'otter-onboarding', { - render: Render -}); +// Check the URL for the onboarding query string. +const urlParams = new URLSearchParams( window.location.search ); +const onboarding = urlParams.get( 'onboarding' ); +console.log( onboarding ); + +// If the onboarding query string is present, render the onboarding modal. +if ( 'true' === onboarding ) { + registerPlugin( 'otter-onboarding', { + render: Render + }); +} diff --git a/src/onboarding/steps.js b/src/onboarding/steps.js index 12defd637..0131829a8 100644 --- a/src/onboarding/steps.js +++ b/src/onboarding/steps.js @@ -13,7 +13,7 @@ import Template from './components/steps/Template'; import Pages from './components/steps/Pages'; const isSupported = step => { - const { supportedSteps } = window.otterObj; + const { supportedSteps } = window.otterOnboardingData; return supportedSteps.includes( step ); }; diff --git a/src/onboarding/style.scss b/src/onboarding/style.scss index 2be25ed16..5170ad136 100644 --- a/src/onboarding/style.scss +++ b/src/onboarding/style.scss @@ -127,7 +127,7 @@ div.error, div.notice { } @media ( min-width: 1920px ) { - width: 25vw; + width: 20vw; } } From f652cdfb1693186da61cf5a3b5e93d073db7595a Mon Sep 17 00:00:00 2001 From: Hardeep Asrani Date: Wed, 29 Nov 2023 17:02:27 +0530 Subject: [PATCH 15/37] Move Onboarding menu to Appearance --- inc/plugins/class-fse-onboarding.php | 2 +- src/onboarding/index.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/inc/plugins/class-fse-onboarding.php b/inc/plugins/class-fse-onboarding.php index eb634ce06..e469b500e 100644 --- a/inc/plugins/class-fse-onboarding.php +++ b/inc/plugins/class-fse-onboarding.php @@ -44,7 +44,7 @@ public function register_menu_page() { } add_submenu_page( - 'otter', + 'themes.php', esc_html__( 'FSE Onboarding', 'otter-blocks' ), esc_html__( 'FSE Onboarding', 'otter-blocks' ), 'manage_options', diff --git a/src/onboarding/index.js b/src/onboarding/index.js index dd85fdb17..c5969c8e4 100644 --- a/src/onboarding/index.js +++ b/src/onboarding/index.js @@ -22,7 +22,6 @@ const Render = () => { // Check the URL for the onboarding query string. const urlParams = new URLSearchParams( window.location.search ); const onboarding = urlParams.get( 'onboarding' ); -console.log( onboarding ); // If the onboarding query string is present, render the onboarding modal. if ( 'true' === onboarding ) { From 08336cbe73af2df5ea31431264928f98b9d687f1 Mon Sep 17 00:00:00 2001 From: Hardeep Asrani Date: Thu, 30 Nov 2023 15:12:35 +0530 Subject: [PATCH 16/37] Small code changes --- inc/plugins/class-fse-onboarding.php | 53 ++++++++++++++++------------ 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/inc/plugins/class-fse-onboarding.php b/inc/plugins/class-fse-onboarding.php index e469b500e..5d92216ab 100644 --- a/inc/plugins/class-fse-onboarding.php +++ b/inc/plugins/class-fse-onboarding.php @@ -14,6 +14,9 @@ */ class FSE_Onboarding { + const OPTION_KEY = 'otter_onboarding_status'; + const SUPPORT_KEY = 'otter-onboarding'; + /** * The main instance var. * @@ -37,7 +40,7 @@ public function init() { * @return void */ public function register_menu_page() { - $has_support = get_theme_support( 'otter-onboarding' ); + $has_support = get_theme_support( self::SUPPORT_KEY ); if ( false === $has_support || ! current_user_can( 'manage_options' ) ) { return; @@ -71,13 +74,13 @@ function() { */ public function on_switch_theme() { // Check if the theme has support for FSE. - $support = get_theme_support( 'otter-onboarding' ); + $support = get_theme_support( self::SUPPORT_KEY ); if ( false === $support ) { return; } - $status = get_option( 'otter_onboarding_status', array() ); + $status = get_option( self::OPTION_KEY, array() ); $slug = get_stylesheet(); if ( ! empty( $status[ $slug ] ) ) { @@ -85,16 +88,6 @@ public function on_switch_theme() { } // Run the onboarding. - self::run_onboarding(); - } - - /** - * Run the onboarding. - * - * @access public - * @return void - */ - public static function run_onboarding() { $redirect = add_query_arg( array( 'onboarding' => 'true', @@ -102,17 +95,29 @@ public static function run_onboarding() { admin_url( 'site-editor.php' ) ); - // Set the onboarding ran option. - $status = get_option( 'otter_onboarding_status', array() ); - $slug = get_stylesheet(); - $status[ $slug ] = true; - update_option( 'otter_onboarding_status', $status ); - // Redirect to the onboarding. wp_safe_redirect( $redirect ); exit; } + /** + * Set Onboarding Status + * + * @access public + * @return void + */ + public static function set_onboarding_status() { + $status = get_option( self::OPTION_KEY, array() ); + $slug = get_stylesheet(); + + if ( ! empty( $status[ $slug ] ) ) { + return; + } + + $status[ $slug ] = true; + update_option( self::OPTION_KEY, $status ); + } + /** * Get Theme Templates * @@ -120,7 +125,7 @@ public static function run_onboarding() { * @return array|false */ public function get_templates() { - $support = get_theme_support( 'otter-onboarding' ); + $support = get_theme_support( self::SUPPORT_KEY ); if ( false === $support || ! is_array( $support ) || ( ! isset( $support[0]['templates'] ) && ! isset( $support[0]['page_templates'] ) ) ) { return false; @@ -151,7 +156,8 @@ public function get_templates() { } } - return $templates; + return apply_filters( 'otter_fse_onboarding_templates', $templates ); + } /** @@ -178,7 +184,7 @@ public function get_templates_types() { */ public function enqueue_options_assets() { $current_screen = get_current_screen(); - $has_support = get_theme_support( 'otter-onboarding' ); + $has_support = get_theme_support( self::SUPPORT_KEY ); if ( false === $has_support || @@ -189,6 +195,9 @@ public function enqueue_options_assets() { return; } + // Flag onboarding status in case being run from a theme. + self::set_onboarding_status(); + $asset_file = include OTTER_BLOCKS_PATH . '/build/onboarding/index.asset.php'; wp_enqueue_media(); From 73559bd369c7897341cfb03de4e04d0f96bd11ed Mon Sep 17 00:00:00 2001 From: Hardeep Asrani Date: Tue, 5 Dec 2023 20:43:32 +0530 Subject: [PATCH 17/37] Add e2e Tests --- .wp-env.override.json | 23 ++- src/blocks/test/e2e/blocks/onboarding.spec.js | 190 ++++++++++++++++++ .../test/performance/config/global-setup.ts | 2 +- .../unit/components/display-time.test.tsx | 2 +- src/blocks/test/unit/utils.test.js | 35 ++++ 5 files changed, 242 insertions(+), 10 deletions(-) create mode 100644 src/blocks/test/e2e/blocks/onboarding.spec.js create mode 100644 src/blocks/test/unit/utils.test.js diff --git a/.wp-env.override.json b/.wp-env.override.json index 52901bf61..29860b07f 100644 --- a/.wp-env.override.json +++ b/.wp-env.override.json @@ -10,6 +10,12 @@ "WP_DEFAULT_THEME": "twentytwentythree" }, "env": { + "development": { + "themes": [ "./test/emptytheme" ], + "mappings": { + "wp-content/themes/raft": "https://github.com/Codeinwp/raft/archive/refs/heads/onboarding.zip" + } + }, "tests": { "config": { "WP_DEBUG": false, @@ -18,14 +24,15 @@ "plugins": [ "." ], - "themes": [ "./test/emptytheme" ], - "mappings": { - "wp-content/mu-plugins": "./packages/e2e-tests/mu-plugins", - "wp-content/plugins/gutenberg-test-plugins": "./packages/e2e-tests/plugins", - "wp-content/themes/gutenberg-test-themes": "./test/gutenberg-test-themes", - "wp-content/themes/gutenberg-test-themes/twentytwentyone": "https://downloads.wordpress.org/theme/twentytwentyone.1.7.zip", - "wp-content/themes/gutenberg-test-themes/twentytwentythree": "https://downloads.wordpress.org/theme/twentytwentythree.1.0.zip" - } + "themes": [ "./test/emptytheme" ], + "mappings": { + "wp-content/mu-plugins": "./packages/e2e-tests/mu-plugins", + "wp-content/plugins/gutenberg-test-plugins": "./packages/e2e-tests/plugins", + "wp-content/themes/gutenberg-test-themes": "./test/gutenberg-test-themes", + "wp-content/themes/gutenberg-test-themes/twentytwentyone": "https://downloads.wordpress.org/theme/twentytwentyone.1.7.zip", + "wp-content/themes/gutenberg-test-themes/twentytwentythree": "https://downloads.wordpress.org/theme/twentytwentythree.1.0.zip", + "wp-content/themes/raft": "https://github.com/Codeinwp/raft/archive/refs/heads/onboarding.zip" + } } } } diff --git a/src/blocks/test/e2e/blocks/onboarding.spec.js b/src/blocks/test/e2e/blocks/onboarding.spec.js new file mode 100644 index 000000000..44f5e0283 --- /dev/null +++ b/src/blocks/test/e2e/blocks/onboarding.spec.js @@ -0,0 +1,190 @@ +/** + * WordPress dependencies + */ +import { test, expect } from '@wordpress/e2e-test-utils-playwright'; +import path from 'path'; + +test.describe( 'FSE Onboarding', () => { + test.beforeEach( async({ admin, page }) => { + const slug = 'raft'; + + await admin.visitAdminPage( 'themes.php' ); + + const activateButton = await page.$( + `div[data-slug="${ slug }"] .button.activate` + ); + + if ( activateButton ) { + await page.click( `div[data-slug="${ slug }"] .button.activate` ); + await page.waitForSelector( `div[data-slug="${ slug }"].active` ); + } + + await admin.visitAdminPage( 'site-editor.php?onboarding=true' ); + + const checkSpinnerGone = async() => { + return await page.evaluate( () => { + + return 0 === document.querySelectorAll( '.components-spinner' ).length; + }); + }; + + // Polling loop to wait for the spinners to disappear + while ( ! await checkSpinnerGone() ) { + await page.waitForTimeout( 100 ); + } + + await page.waitForSelector( '.o-main iframe' ); + }); + + test( 'onboarding is available in site Editor', async({ page }) => { + await page.waitForSelector( '#otter-onboarding' ); + expect( await page.isVisible( '#otter-onboarding' ) ).toBe( true ); + }); + + test( 'test onboarding flow', async({ page }) => { + const getCurrentStep = async() => { + return await page.evaluate( () => { + return wp.data.select( 'otter/onboarding' ).getStep()?.id; + }); + }; + + expect( await getCurrentStep() ).toBe( 'site_info' ); + + const siteTitle = page.getByPlaceholder( 'Acme Corporation' ); + const ourTitle = 'Test Title'; + await siteTitle.clear(); + await siteTitle.type( ourTitle ); + + const title = await page.evaluate( () => { + const title = document.querySelector( '.o-main iframe' ).contentWindow.document.querySelector( '[aria-label="Site title text"]' ).innerHTML; + return title; + }); + + expect( title ).toBe( ourTitle ); + + const next = page.getByRole( 'button', { name: 'Continue' }); + await next.click(); + + const checkSpinnerGone = async() => { + return await page.evaluate( () => { + + return 0 === document.querySelectorAll( '.components-spinner' ).length; + }); + }; + + // Polling loop to wait for the spinners to disappear + while ( ! await checkSpinnerGone() ) { + await page.waitForTimeout( 100 ); + } + + expect( await getCurrentStep() ).toBe( 'appearance' ); + + await page.evaluate( () => { + const palette = document.querySelectorAll( '.o-palettes .o-palette' )[2]; + palette.click(); + }); + + const isSelectedPalette = await page.evaluate( () => { + const palette = document.querySelectorAll( '.o-palettes .o-palette' )[2]; + return palette.classList.contains( 'is-selected' ); + }); + + expect( isSelectedPalette ).toBe( true ); + + await next.click(); + + while ( ! await checkSpinnerGone() ) { + await page.waitForTimeout( 100 ); + } + + expect( await getCurrentStep() ).toBe( 'front-page_template' ); + + await page.waitForTimeout( 1000 ); + + await page.evaluate( () => { + const template = document.querySelectorAll( '.o-templates .o-templates__item' )[1]; + template.click(); + }); + + const isSelectedFPTemplate = await page.evaluate( () => { + const template = document.querySelectorAll( '.o-templates .o-templates__item' )[1]; + return template.querySelector( '.o-templates__item__container' ).classList.contains( 'is-selected' ); + }); + + expect( isSelectedFPTemplate ).toBe( true ); + + await next.click(); + + while ( ! await checkSpinnerGone() ) { + await page.waitForTimeout( 100 ); + } + + expect( await getCurrentStep() ).toBe( 'archive_template' ); + + await page.waitForTimeout( 1000 ); + + await page.evaluate( () => { + const template = document.querySelectorAll( '.o-templates .o-templates__item' )[1]; + template.click(); + }); + + const isSelectedArchive = await page.evaluate( () => { + const template = document.querySelectorAll( '.o-templates .o-templates__item' )[1]; + return template.querySelector( '.o-templates__item__container' ).classList.contains( 'is-selected' ); + }); + + expect( isSelectedArchive ).toBe( true ); + + await next.click(); + + while ( ! await checkSpinnerGone() ) { + await page.waitForTimeout( 100 ); + } + + expect( await getCurrentStep() ).toBe( 'single_template' ); + + await page.waitForTimeout( 1000 ); + + await page.evaluate( () => { + const template = document.querySelectorAll( '.o-templates .o-templates__item' )[1]; + template.click(); + }); + + const isSelectedSingle = await page.evaluate( () => { + const template = document.querySelectorAll( '.o-templates .o-templates__item' )[1]; + return template.querySelector( '.o-templates__item__container' ).classList.contains( 'is-selected' ); + }); + + expect( isSelectedSingle ).toBe( true ); + + await next.click(); + + while ( ! await checkSpinnerGone() ) { + await page.waitForTimeout( 100 ); + } + + await page.waitForTimeout( 1000 ); + + expect( await getCurrentStep() ).toBe( 'additional_templates' ); + + await page.waitForTimeout( 1000 ); + + const getSelectedTemplates = async() => { + return await page.evaluate( () => { + return wp.data.select( 'otter/onboarding' ).getSelectedTemplate( 'pageTemplates' ); + }); + }; + + const templates = await page.evaluate( () => { + const templates = document.querySelectorAll( '.o-templates .o-templates__item' ); + + for ( let i = 0; i < templates.length; i++ ) { + templates[i].click(); + } + + return templates; + }); + + expect( await getSelectedTemplates()?.length ).toBe( templates.length ); + }); +}); diff --git a/src/blocks/test/performance/config/global-setup.ts b/src/blocks/test/performance/config/global-setup.ts index d7ceb5e5c..912c26f95 100644 --- a/src/blocks/test/performance/config/global-setup.ts +++ b/src/blocks/test/performance/config/global-setup.ts @@ -27,7 +27,7 @@ async function globalSetup( config: FullConfig ) { // Reset the test environment before running the tests. await Promise.all([ - requestUtils.activateTheme( 'twentytwentyone' ), + requestUtils.activateTheme( 'raft' ), // Disable this test plugin as it's conflicting with some of the tests. // We already have reduced motion enabled and Playwright will wait for most of the animations anyway. diff --git a/src/blocks/test/unit/components/display-time.test.tsx b/src/blocks/test/unit/components/display-time.test.tsx index 02fba5ee4..e6bf6d7e4 100644 --- a/src/blocks/test/unit/components/display-time.test.tsx +++ b/src/blocks/test/unit/components/display-time.test.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react'; +import { render } from '@testing-library/react'; import '@testing-library/jest-dom'; import DisplayTime from '../../../blocks/countdown/components/display-time'; diff --git a/src/blocks/test/unit/utils.test.js b/src/blocks/test/unit/utils.test.js new file mode 100644 index 000000000..06372842a --- /dev/null +++ b/src/blocks/test/unit/utils.test.js @@ -0,0 +1,35 @@ +import { findBlock } from '../../../onboarding/utils'; + +describe( 'findBlock', () => { + const blocks = [ + { + name: 'block1', + innerBlocks: [ + { + name: 'block2' + }, + { + name: 'block3' + } + ] + }, + { + name: 'block4' + } + ]; + + test( 'should find the block if it exists in the top-level blocks', () => { + const result = findBlock( blocks, 'block4' ); + expect( result ).toEqual({ name: 'block4' }); + }); + + test( 'should find the block if it exists in the inner blocks', () => { + const result = findBlock( blocks, 'block2' ); + expect( result ).toEqual({ name: 'block2' }); + }); + + test( 'should return undefined if the block does not exist', () => { + const result = findBlock( blocks, 'block5' ); + expect( result ).toBeUndefined(); + }); +}); From 4c07ac882a47ed033a7a0ee32a1958034f7e9323 Mon Sep 17 00:00:00 2001 From: Hardeep Asrani Date: Fri, 8 Dec 2023 13:31:23 +0530 Subject: [PATCH 18/37] Add event tracking --- inc/plugins/class-fse-onboarding.php | 3 + src/onboarding/components/App.js | 7 ++ src/onboarding/components/Sidebar.js | 2 +- src/onboarding/components/steps/Appearance.js | 20 ++++- src/onboarding/components/steps/SiteInfo.js | 26 +++++- src/onboarding/store.js | 79 ++++++++++++++++++- src/onboarding/utils.js | 70 ++++++++++++++++ 7 files changed, 200 insertions(+), 7 deletions(-) diff --git a/inc/plugins/class-fse-onboarding.php b/inc/plugins/class-fse-onboarding.php index 5d92216ab..fda6ff4a8 100644 --- a/inc/plugins/class-fse-onboarding.php +++ b/inc/plugins/class-fse-onboarding.php @@ -228,6 +228,9 @@ public function enqueue_options_assets() { 'version' => OTTER_BLOCKS_VERSION, 'assetsPath' => OTTER_BLOCKS_URL . 'assets/', 'supportedSteps' => $this->get_templates_types(), + 'license' => apply_filters( 'product_otter_license_key', 'free' ), + 'rootUrl' => get_site_url(), + 'isDev' => defined( 'ENABLE_OTTER_PRO_DEV' ) ) ) ); diff --git a/src/onboarding/components/App.js b/src/onboarding/components/App.js index a1d5179d1..a262a7398 100644 --- a/src/onboarding/components/App.js +++ b/src/onboarding/components/App.js @@ -5,6 +5,8 @@ import { __ } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; +import { useEffect } from '@wordpress/element'; + /** * Internal dependencies. */ @@ -12,6 +14,7 @@ import Finish from './Finish'; import Sidebar from './Sidebar'; import Main from './Main'; import { useIsSiteEditorLoading } from '../hooks'; +import { recordEvent } from '../utils'; const App = () => { const isEditorLoading = useIsSiteEditorLoading(); @@ -31,6 +34,10 @@ const App = () => { }; }, []); + useEffect( () => { + recordEvent(); + }, []); + if ( isFinished ) { return (
diff --git a/src/onboarding/components/Sidebar.js b/src/onboarding/components/Sidebar.js index ad886558c..843bd3516 100644 --- a/src/onboarding/components/Sidebar.js +++ b/src/onboarding/components/Sidebar.js @@ -121,7 +121,7 @@ const Sidebar = ({ isEditorLoading }) => { { ! STEP_DATA[ currentStep ]?.hideSkip && ( diff --git a/src/onboarding/components/steps/Appearance.js b/src/onboarding/components/steps/Appearance.js index 0b6f0dfae..97ef0a0b2 100644 --- a/src/onboarding/components/steps/Appearance.js +++ b/src/onboarding/components/steps/Appearance.js @@ -50,6 +50,8 @@ const Appearance = () => { const { editEntityRecord } = useDispatch( 'core' ); + const { setChangedData } = useDispatch( 'otter/onboarding' ); + const selectedStyle = useMemo( () => { if ( ! Object.keys( globalStyle?.styles ).length && ! Object.keys( globalStyle?.settings ).length ) { return 'default'; @@ -63,8 +65,16 @@ const Appearance = () => { return foundStyle ? hash( foundStyle ) : false; }, [ globalStyle, themeStyles ]); - const onSelect = ( style ) => { + const onSelect = style => { if ( 'default' === style ) { + setChangedData({ + // eslint-disable-next-line camelcase + design_choices: { + // eslint-disable-next-line camelcase + palette: 'default' + } + }); + editEntityRecord( 'root', 'globalStyles', globalStyle.id, { styles: {}, settings: {} @@ -72,6 +82,14 @@ const Appearance = () => { return; } + setChangedData({ + // eslint-disable-next-line camelcase + design_choices: { + // eslint-disable-next-line camelcase + palette: style.title + } + }); + editEntityRecord( 'root', 'globalStyles', globalStyle.id, { styles: style?.styles, settings: style?.settings diff --git a/src/onboarding/components/steps/SiteInfo.js b/src/onboarding/components/steps/SiteInfo.js index e134ecdff..d582f4bd3 100644 --- a/src/onboarding/components/steps/SiteInfo.js +++ b/src/onboarding/components/steps/SiteInfo.js @@ -27,7 +27,8 @@ const SiteInfo = () => { title, siteLogo, siteLogoURL, - templateParts + templateParts, + changedData } = useSelect( select => { const { getEditedEntityRecord, @@ -36,26 +37,39 @@ const SiteInfo = () => { const { getCurrentTemplateTemplateParts } = select( 'core/edit-site' ); + const { getChangedData } = select( 'otter/onboarding' ); + const settings = getEditedEntityRecord( 'root', 'site' ); const siteLogoURL = settings?.site_logo ? getMedia( settings?.site_logo, { context: 'view' }) : null; const templateParts = getCurrentTemplateTemplateParts(); + const changedData = getChangedData(); return { title: settings?.title, siteLogo: settings?.site_logo, siteLogoURL: siteLogoURL?.source_url, - templateParts + templateParts, + changedData }; }, []); const { editEntityRecord } = useDispatch( 'core' ); const { replaceBlock } = useDispatch( 'core/block-editor' ); const { setEditedEntity } = useDispatch( 'core/edit-site' ); + const { setChangedData } = useDispatch( 'otter/onboarding' ); const setTitle = newTitle => { editEntityRecord( 'root', 'site', undefined, { title: newTitle }); + + setChangedData({ + // eslint-disable-next-line camelcase + fields_filled: { + ...changedData.fields_filled, + siteTitle: true + } + }); }; const onOpenMedia = open => { @@ -90,6 +104,14 @@ const SiteInfo = () => { } } + setChangedData({ + // eslint-disable-next-line camelcase + fields_filled: { + ...changedData.fields_filled, + siteLogo: true + } + }); + editEntityRecord( 'root', 'site', undefined, { 'site_logo': newLogo?.id }); diff --git a/src/onboarding/store.js b/src/onboarding/store.js index 48e53357a..fe40654cb 100644 --- a/src/onboarding/store.js +++ b/src/onboarding/store.js @@ -1,3 +1,4 @@ +/* eslint-disable camelcase */ /** * WordPress dependencies. */ @@ -16,6 +17,7 @@ import { addQueryArgs } from '@wordpress/url'; * Internal dependencies. */ import STEP_DATA from './steps'; +import { recordEvent } from './utils'; const STEPS = Object.keys( STEP_DATA ) .filter( i => !! STEP_DATA[i].isSupported ) @@ -44,8 +46,15 @@ const DEFAULT_STATE = { pageTemplates: [] }, importedTemplates: [], + changedData: { + design_choices: { + palette: 'default' + }, + fields_filled: {} + }, isSaving: false, - isFinished: false + isFinished: false, + sessionID: '' }; const actions = { @@ -55,7 +64,7 @@ const actions = { step }; }, - nextStep() { + nextStep( isSkip = false ) { return ({ dispatch, select }) => { const step = select.getStep(); const isLast = STEPS.length === ( step.value + 1 ); @@ -64,10 +73,22 @@ const actions = { dispatch( actions.setSaving( false ) ); if ( isLast ) { + recordEvent({ + step_id: STEPS.length, + step_status: 'completed' + }); + dispatch( actions.setFinished( true ) ); return; } + if ( isSkip ) { + recordEvent({ + step_id: step.value + 1, + step_status: 'skip' + }); + } + dispatch( actions.setStep( newStep ) ); }; }, @@ -90,6 +111,13 @@ const actions = { onContinue() { return async({ dispatch, select }) => { const step = select.getStep(); + const changedData = select.getChangedData(); + + let event = { + type: step.id, + step_id: step.value + 1, + step_status: 'completed' + }; dispatch( actions.setSaving( true ) ); @@ -98,6 +126,8 @@ const actions = { const selectedTemplate = select.getSelectedTemplate( type ); + event.selected_template = selectedTemplate; + if ( ! selectedTemplate ) { dispatch( actions.nextStep() ); return; @@ -127,10 +157,20 @@ const actions = { }) ); } + if ( 'site_info' === step.id ) { + event.fields_filled = changedData.fields_filled; + } + + if ( 'appearance' === step.id ) { + event.design_choices = changedData.design_choices; + } + if ( 'additional_templates' === step.id ) { const selectedTemplates = select.getSelectedTemplate( 'pageTemplates' ); const pageTemplates = select.getLibrary( 'page_templates' ); - const importedTemplates = select.getImportedTemplates(); // It will be array similar to selectedTemplates + const importedTemplates = select.getImportedTemplates(); + + event.imported_items = selectedTemplates; await Promise.all( selectedTemplates @@ -149,6 +189,8 @@ const actions = { }) ); } + recordEvent( event ); + dispatch( actions.setSaving( false ) ); dispatch( actions.nextStep() ); }; @@ -196,6 +238,18 @@ const actions = { isSaving }; }, + setSessionID( sessionID ) { + return { + type: 'SET_SESSION_ID', + sessionID + }; + }, + setChangedData( data ) { + return { + type: 'SET_CHANGED_DATA', + data + }; + }, fetchFromAPI( path ) { return { type: 'FETCH_FROM_API', @@ -267,6 +321,19 @@ const store = createReduxStore( 'otter/onboarding', { ...state, isSaving: action.isSaving }; + case 'SET_SESSION_ID': + return { + ...state, + sessionID: action.sessionID + }; + case 'SET_CHANGED_DATA': + return { + ...state, + changedData: { + ...state.changedData, + ...action.data + } + }; } return state; @@ -305,6 +372,12 @@ const store = createReduxStore( 'otter/onboarding', { }, isFinished( state ) { return state.isFinished; + }, + getSessionID( state ) { + return state.sessionID; + }, + getChangedData( state ) { + return state.changedData; } }, diff --git a/src/onboarding/utils.js b/src/onboarding/utils.js index d9150ca5c..bebbf60df 100644 --- a/src/onboarding/utils.js +++ b/src/onboarding/utils.js @@ -1,3 +1,11 @@ +/** + * WordPress dependencies. + */ +import { + dispatch, + select +} from '@wordpress/data'; + export const findBlock = ( blocksAr, name ) => { const foundBlock = blocksAr.find( block => block.name === name ); @@ -17,3 +25,65 @@ export const findBlock = ( blocksAr, name ) => { return undefined; }, undefined ); }; + +export const recordEvent = async( data = {}) => { + if ( window.otterOnboardingData?.isDev ) { + return; + } + + const { setSessionID } = dispatch( 'otter/onboarding' ); + const { getSessionID } = select( 'otter/onboarding' ); + const { getCurrentTheme } = select( 'core' ); + + const trackingId = getSessionID(); + + try { + const response = await fetch( + 'https://api.themeisle.com/tracking/onboarding', + { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + _id: trackingId, + data: { + slug: getCurrentTheme()?.template || getCurrentTheme()?.stylesheet, + // eslint-disable-next-line camelcase + license_id: window.otterOnboardingData?.license, + site: window.otterOnboardingData?.rootUrl || '', + ...data + } + }) + } + ); + + if ( ! response.ok ) { + console.error( `HTTP error! Status: ${ response.status }` ); + return false; + } + + const jsonResponse = await response.json(); + + const validCodes = [ 'success', 'invalid' ]; // Add valid codes to this array + + if ( ! validCodes.includes( jsonResponse.code ) ) { + return false; + } + + if ( 'invalid' === jsonResponse.code ) { + console.error( jsonResponse.message ); + return false; + } + const responseData = jsonResponse.data; + + if ( responseData?.id && '' === trackingId ) { + setSessionID( responseData.id ); + } + + return responseData.id || false; + } catch ( error ) { + console.error( error ); + return false; + } +}; From fe77f8251793ebfac744866b0e399364a6a4d1f8 Mon Sep 17 00:00:00 2001 From: Hardeep Asrani Date: Fri, 8 Dec 2023 13:33:45 +0530 Subject: [PATCH 19/37] Update readme --- readme.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readme.txt b/readme.txt index b5c5de158..95cd0c7e3 100644 --- a/readme.txt +++ b/readme.txt @@ -265,6 +265,8 @@ Otter Blocks is an open-source project, and we welcome contributors to be part o - Interested in our other plugins? Take a look [here](https://themeisle.com/wordpress-plugins/?utm_source=wpadmin&utm_medium=readme&utm_campaign=otter-blocks). - Get the most out of Otter Blocks with our helpful [YouTube Tutorials Playlist](https://youtube.com/playlist?list=PLmRasCVwuvpSep2MOsIoE0ncO9JE3FcKP). +The plugin is relying on the service behind api.themeisle.com for accessing the patterns list, AI prompts and Onboarding. No account is required to access the service template collection and the privacy policy can be found [here](https://themeisle.com/privacy-policy/). + == Screenshots == 1. Gutenberg Block Animations From ccd22ec940e0bb14052bbda44895158cb1a37d9c Mon Sep 17 00:00:00 2001 From: Hardeep Asrani Date: Mon, 11 Dec 2023 22:29:09 +0530 Subject: [PATCH 20/37] Add Welcome Screen --- inc/plugins/class-fse-onboarding.php | 1 + src/blocks/test/e2e/blocks/onboarding.spec.js | 23 +++++++- src/onboarding/components/App.js | 16 +++++- src/onboarding/components/Start.js | 44 +++++++++++++++ src/onboarding/store.js | 15 +++++ src/onboarding/style.scss | 56 ++++++++++++++++++- 6 files changed, 151 insertions(+), 4 deletions(-) create mode 100644 src/onboarding/components/Start.js diff --git a/inc/plugins/class-fse-onboarding.php b/inc/plugins/class-fse-onboarding.php index fda6ff4a8..0ac650378 100644 --- a/inc/plugins/class-fse-onboarding.php +++ b/inc/plugins/class-fse-onboarding.php @@ -230,6 +230,7 @@ public function enqueue_options_assets() { 'supportedSteps' => $this->get_templates_types(), 'license' => apply_filters( 'product_otter_license_key', 'free' ), 'rootUrl' => get_site_url(), + 'dashboardUrl' => get_admin_url(), 'isDev' => defined( 'ENABLE_OTTER_PRO_DEV' ) ) ) diff --git a/src/blocks/test/e2e/blocks/onboarding.spec.js b/src/blocks/test/e2e/blocks/onboarding.spec.js index 44f5e0283..9a874d404 100644 --- a/src/blocks/test/e2e/blocks/onboarding.spec.js +++ b/src/blocks/test/e2e/blocks/onboarding.spec.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { test, expect } from '@wordpress/e2e-test-utils-playwright'; -import path from 'path'; test.describe( 'FSE Onboarding', () => { test.beforeEach( async({ admin, page }) => { @@ -21,6 +20,16 @@ test.describe( 'FSE Onboarding', () => { await admin.visitAdminPage( 'site-editor.php?onboarding=true' ); + const hasWelcomeNotice = async() => { + return await page.evaluate( () => { + return wp.data.select( 'otter/onboarding' ).isWelcomeScreen(); + }); + }; + + if ( hasWelcomeNotice ) { + await page.getByRole( 'button', { name: 'Set up my theme' }).click(); + } + const checkSpinnerGone = async() => { return await page.evaluate( () => { @@ -186,5 +195,17 @@ test.describe( 'FSE Onboarding', () => { }); expect( await getSelectedTemplates()?.length ).toBe( templates.length ); + + await next.click(); + + while ( ! await checkSpinnerGone() ) { + await page.waitForTimeout( 100 ); + } + + await page.waitForTimeout( 1000 ); + + const button = await page.getByRole( 'button', { name: 'Visit your website' }); + + expect( await button.isVisible() ).toBe( true ); }); }); diff --git a/src/onboarding/components/App.js b/src/onboarding/components/App.js index a262a7398..e53fae525 100644 --- a/src/onboarding/components/App.js +++ b/src/onboarding/components/App.js @@ -11,6 +11,7 @@ import { useEffect } from '@wordpress/element'; * Internal dependencies. */ import Finish from './Finish'; +import Start from './Start'; import Sidebar from './Sidebar'; import Main from './Main'; import { useIsSiteEditorLoading } from '../hooks'; @@ -21,15 +22,20 @@ const App = () => { const { isFinished, + isWelcomeScreen, isSmall } = useSelect( select => { - const { isFinished } = select( 'otter/onboarding' ); + const { + isFinished, + isWelcomeScreen + } = select( 'otter/onboarding' ); const { isViewportMatch } = select( 'core/viewport' ); const isSmall = isViewportMatch( '< medium' ); return { isFinished: isFinished(), + isWelcomeScreen: isWelcomeScreen(), isSmall }; }, []); @@ -38,6 +44,14 @@ const App = () => { recordEvent(); }, []); + if ( isWelcomeScreen ) { + return ( +
+ +
+ ); + } + if ( isFinished ) { return (
diff --git a/src/onboarding/components/Start.js b/src/onboarding/components/Start.js new file mode 100644 index 000000000..df51045c1 --- /dev/null +++ b/src/onboarding/components/Start.js @@ -0,0 +1,44 @@ +/** + * WordPress dependencies. + */ +import { __ } from '@wordpress/i18n'; + +import { Button } from '@wordpress/components'; + +import { useDispatch } from '@wordpress/data'; + +const Start = () => { + const { setWelcomeScreen } = useDispatch( 'otter/onboarding' ); + + return ( +
+
+ + +

{ __( 'Welcome to the Theme Onboarding Wizard by Otter.', 'otter-blocks' ) }

+

{ __( 'This process will help you go through a basic setup of your theme, so you can start customising your site right away. You can trigger this wizard manually at any time, from Appearance Menu.', 'otter-blocks' ) }

+ +
+ + + +
+
+
+ ); +}; + +export default Start; diff --git a/src/onboarding/store.js b/src/onboarding/store.js index fe40654cb..8368e52be 100644 --- a/src/onboarding/store.js +++ b/src/onboarding/store.js @@ -54,6 +54,7 @@ const DEFAULT_STATE = { }, isSaving: false, isFinished: false, + isWelcomeScreen: true, sessionID: '' }; @@ -108,6 +109,12 @@ const actions = { isFinished }; }, + setWelcomeScreen( isWelcomeScreen ) { + return { + type: 'SET_WELCOME_SCREEN', + isWelcomeScreen + }; + }, onContinue() { return async({ dispatch, select }) => { const step = select.getStep(); @@ -271,6 +278,11 @@ const store = createReduxStore( 'otter/onboarding', { ...state, isFinished: action.isFinished }; + case 'SET_WELCOME_SCREEN': + return { + ...state, + isWelcomeScreen: action.isWelcomeScreen + }; case 'SET_TEMPLATE': return { ...state, @@ -373,6 +385,9 @@ const store = createReduxStore( 'otter/onboarding', { isFinished( state ) { return state.isFinished; }, + isWelcomeScreen( state ) { + return state.isWelcomeScreen; + }, getSessionID( state ) { return state.sessionID; }, diff --git a/src/onboarding/style.scss b/src/onboarding/style.scss index 5170ad136..fe689194f 100644 --- a/src/onboarding/style.scss +++ b/src/onboarding/style.scss @@ -29,6 +29,7 @@ div.error, div.notice { z-index: 100000; background: $main-bg; overflow: auto; + --wp-components-color-accent: #0366D5; } .o-onboarding { @@ -37,7 +38,6 @@ div.error, div.notice { width: 100vw; height: 100vh; font-family: Arial,sans-serif; - --wp-components-color-accent: #0366D5; @media ( max-width: 600px ) { display: block; @@ -52,6 +52,58 @@ div.error, div.notice { } } +.o-start { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + background: #fff; + + &__container { + padding: 5rem; + max-width: 726px; + + h1 { + font-size: 40px; + font-style: normal; + font-weight: 700; + line-height: 1; + } + + p { + font-size: 18px; + font-style: normal; + font-weight: 400; + } + + .components-button.is-primary { + @include primary-button-styles; + margin-top: 1rem; + } + + .components-button.is-tertiary { + color: $secondary-text; + padding: 16px 32px; + margin-top: 1rem; + font-size: 18px; + font-style: normal; + font-weight: 700; + height: auto; + } + } + + &__logo { + width: 120px; + height: 120px; + } + + &__actions { + display: flex; + align-items: center; + gap: 16px; + } +} + .o-finish { display: flex; justify-content: center; @@ -62,7 +114,7 @@ div.error, div.notice { background: #fff; border-radius: 32px; padding: 5rem; - max-width: 980px; + max-width: 726px; h1 { font-size: 40px; From 23c3942998b8c7e835a0fcce74990dc407549abd Mon Sep 17 00:00:00 2001 From: Hardeep Asrani Date: Mon, 11 Dec 2023 22:34:38 +0530 Subject: [PATCH 21/37] chore: fix phpcs --- inc/plugins/class-fse-onboarding.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/plugins/class-fse-onboarding.php b/inc/plugins/class-fse-onboarding.php index 0ac650378..316c2ae3d 100644 --- a/inc/plugins/class-fse-onboarding.php +++ b/inc/plugins/class-fse-onboarding.php @@ -228,10 +228,10 @@ public function enqueue_options_assets() { 'version' => OTTER_BLOCKS_VERSION, 'assetsPath' => OTTER_BLOCKS_URL . 'assets/', 'supportedSteps' => $this->get_templates_types(), - 'license' => apply_filters( 'product_otter_license_key', 'free' ), + 'license' => apply_filters( 'product_otter_license_key', 'free' ), 'rootUrl' => get_site_url(), 'dashboardUrl' => get_admin_url(), - 'isDev' => defined( 'ENABLE_OTTER_PRO_DEV' ) + 'isDev' => defined( 'ENABLE_OTTER_PRO_DEV' ), ) ) ); From ebe7cbde65faa9ba43b4ba5fb9e789e11cb14071 Mon Sep 17 00:00:00 2001 From: Hardeep Asrani Date: Wed, 13 Dec 2023 02:41:27 +0530 Subject: [PATCH 22/37] Allow PHP Templates --- inc/plugins/class-fse-onboarding.php | 8 +++++++- src/onboarding/README.md | 1 + src/onboarding/style.scss | 1 - 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/inc/plugins/class-fse-onboarding.php b/inc/plugins/class-fse-onboarding.php index 316c2ae3d..65e4e6650 100644 --- a/inc/plugins/class-fse-onboarding.php +++ b/inc/plugins/class-fse-onboarding.php @@ -148,7 +148,13 @@ public function get_templates() { foreach ( $templates as $key => $categories ) { foreach ( $categories as $i => $template ) { if ( file_exists( $template['file'] ) ) { - $templates[ $key ][ $i ]['content']['raw'] = file_get_contents( $template['file'] ); + if ( 'php' === pathinfo( $template['file'], PATHINFO_EXTENSION ) ) { + $content = include $template['file']; + $templates[ $key ][ $i ]['content']['raw'] = $content; + } else { + $templates[ $key ][ $i ]['content']['raw'] = file_get_contents( $template['file'] ); + } + unset( $templates[ $key ][ $i ]['file'] ); } else { unset( $templates[ $key ][ $i ] ); diff --git a/src/onboarding/README.md b/src/onboarding/README.md index 6c24e4ddf..6fd6baef1 100644 --- a/src/onboarding/README.md +++ b/src/onboarding/README.md @@ -82,5 +82,6 @@ Specify page templates for onboarding import. ## Notes - Modify file paths and titles to suit your theme's structure and branding. +- File types can either be HTML or PHP. If they are PHP, they must return the template content. This allows us to use translatable strings & dynamic images. - Localize titles for translation readiness. - Adhere to the array structure for proper recognition and import by the onboarding process. diff --git a/src/onboarding/style.scss b/src/onboarding/style.scss index fe689194f..447ebb850 100644 --- a/src/onboarding/style.scss +++ b/src/onboarding/style.scss @@ -392,7 +392,6 @@ div.error, div.notice { .block-editor-block-preview__container { .block-editor-block-preview__content, iframe { - height: 100% !important; max-height: none !important; } } From 23870fe44409cb8dc0da2efb9e557e08af6ab5fe Mon Sep 17 00:00:00 2001 From: John Fraskos Date: Wed, 13 Dec 2023 17:46:59 +0200 Subject: [PATCH 23/37] Update Start.js Changed text in title and description. Need to add a link to the read more. --- src/onboarding/components/Start.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/onboarding/components/Start.js b/src/onboarding/components/Start.js index df51045c1..6eb861755 100644 --- a/src/onboarding/components/Start.js +++ b/src/onboarding/components/Start.js @@ -18,8 +18,8 @@ const Start = () => { src={ `${ window.otterOnboardingData.assetsPath }images/logo-alt.png` } /> -

{ __( 'Welcome to the Theme Onboarding Wizard by Otter.', 'otter-blocks' ) }

-

{ __( 'This process will help you go through a basic setup of your theme, so you can start customising your site right away. You can trigger this wizard manually at any time, from Appearance Menu.', 'otter-blocks' ) }

+

{ __( 'Welcome to FSE onboarding, by Otter.', 'otter-blocks' ) }

+

{ __( 'This process will guide you through a basic setup of your theme, so you can enjoy your new site right away. You can trigger this wizard manually at any time, from Appearance Menu. Learn more.', 'otter-blocks' ) }

+
+ + + +
); diff --git a/src/onboarding/style.scss b/src/onboarding/style.scss index 447ebb850..7d1849037 100644 --- a/src/onboarding/style.scss +++ b/src/onboarding/style.scss @@ -52,12 +52,12 @@ div.error, div.notice { } } -.o-start { +.o-start, +.o-finish { display: flex; justify-content: center; align-items: center; height: 100%; - background: #fff; &__container { padding: 5rem; @@ -76,6 +76,14 @@ div.error, div.notice { font-weight: 400; } + .components-textarea-control__input { + border-radius: 6px; + border: 1px solid $border-color; + background: #FFF; + padding: 1rem; + resize: none; + } + .components-button.is-primary { @include primary-button-styles; margin-top: 1rem; @@ -104,48 +112,14 @@ div.error, div.notice { } } -.o-finish { - display: flex; - justify-content: center; - align-items: center; - height: 100%; +.o-start { + background: #fff; +} +.o-finish { &__container { background: #fff; border-radius: 32px; - padding: 5rem; - max-width: 726px; - - h1 { - font-size: 40px; - font-style: normal; - font-weight: 700; - line-height: 1; - } - - p { - font-size: 18px; - font-style: normal; - font-weight: 400; - } - - .components-textarea-control__input { - border-radius: 6px; - border: 1px solid $border-color; - background: #FFF; - padding: 1rem; - resize: none; - } - - .components-button.is-primary { - @include primary-button-styles; - margin-top: 1rem; - } - } - - &__logo { - width: 120px; - height: 120px; } } From 42170c640fb202fa94948841c3c84fd4af2d2c50 Mon Sep 17 00:00:00 2001 From: Hardeep Asrani Date: Fri, 15 Dec 2023 00:54:10 +0530 Subject: [PATCH 30/37] Show textarea label in Finish step --- src/onboarding/components/Finish.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/onboarding/components/Finish.js b/src/onboarding/components/Finish.js index b06bba9fe..3b9893a00 100644 --- a/src/onboarding/components/Finish.js +++ b/src/onboarding/components/Finish.js @@ -79,7 +79,6 @@ const Finish = () => { placeholder={ __( 'Leave your feedback', 'otter-blocks' ) } value={ feedback } onChange={ setFeedback } - hideLabelFromVision />
From 37bedf14718093a78c1e140a82f93c82a8235dd0 Mon Sep 17 00:00:00 2001 From: John Fraskos Date: Fri, 15 Dec 2023 13:25:39 +0200 Subject: [PATCH 31/37] Update Start.js Removed FSE reference from copy --- src/onboarding/components/Start.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/onboarding/components/Start.js b/src/onboarding/components/Start.js index d57d5dfa3..587c0041a 100644 --- a/src/onboarding/components/Start.js +++ b/src/onboarding/components/Start.js @@ -21,7 +21,7 @@ const Start = () => { src={ `${ window.otterOnboardingData.assetsPath }images/logo-alt.png` } /> -

{ __( 'Welcome to FSE onboarding, by Otter.', 'otter-blocks' ) }

+

{ __( 'Welcome to Theme Onboarding, by Otter.', 'otter-blocks' ) }

{ __( 'This process will guide you through a basic setup of your theme, so you can enjoy your new site right away. You can trigger this wizard manually at any time, from Appearance Menu.', 'otter-blocks' ) } From 5a842d0c151fd065f291c16debb85233fb5b0e9d Mon Sep 17 00:00:00 2001 From: John Fraskos Date: Fri, 15 Dec 2023 14:06:53 +0200 Subject: [PATCH 32/37] Update Dashboard.js changed copy in the onboarding toggle. --- src/dashboard/components/pages/Dashboard.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dashboard/components/pages/Dashboard.js b/src/dashboard/components/pages/Dashboard.js index aba8ebed5..4dfcf2080 100644 --- a/src/dashboard/components/pages/Dashboard.js +++ b/src/dashboard/components/pages/Dashboard.js @@ -252,8 +252,8 @@ const Dashboard = () => { applyAction({ type: 'update', name: 'enableOnboardingWizard', value }) } From 73db521120cedfa6f7d3a736f7828e050dd92d6f Mon Sep 17 00:00:00 2001 From: Hardeep Asrani Date: Sat, 16 Dec 2023 03:16:59 +0530 Subject: [PATCH 33/37] Add Email Subscription --- inc/plugins/class-fse-onboarding.php | 5 +- src/onboarding/components/Finish.js | 70 ++++++++++++++++++---------- src/onboarding/style.scss | 23 +++++++-- 3 files changed, 68 insertions(+), 30 deletions(-) diff --git a/inc/plugins/class-fse-onboarding.php b/inc/plugins/class-fse-onboarding.php index 228cb3483..a9e4e2197 100644 --- a/inc/plugins/class-fse-onboarding.php +++ b/inc/plugins/class-fse-onboarding.php @@ -48,8 +48,8 @@ public function register_menu_page() { add_submenu_page( 'themes.php', - esc_html__( 'FSE Onboarding', 'otter-blocks' ), - esc_html__( 'FSE Onboarding', 'otter-blocks' ), + esc_html__( 'Theme Setup', 'otter-blocks' ), + esc_html__( 'Theme Setup', 'otter-blocks' ), 'manage_options', 'otter-onboarding', function() { @@ -238,6 +238,7 @@ public function enqueue_options_assets() { 'rootUrl' => get_site_url(), 'dashboardUrl' => get_admin_url(), 'isDev' => defined( 'ENABLE_OTTER_PRO_DEV' ), + 'userEmail' => wp_get_current_user()->user_email, ) ) ); diff --git a/src/onboarding/components/Finish.js b/src/onboarding/components/Finish.js index 3b9893a00..8a3851204 100644 --- a/src/onboarding/components/Finish.js +++ b/src/onboarding/components/Finish.js @@ -5,40 +5,52 @@ import { __ } from '@wordpress/i18n'; import { Button, - TextareaControl + CheckboxControl, + TextControl } from '@wordpress/components'; -import { useSelect } from '@wordpress/data'; +import { + useDispatch, + useSelect +} from '@wordpress/data'; import { useState } from '@wordpress/element'; -const { version } = window.otterOnboardingData; +import { create } from '@wordpress/preferences-persistence'; const Finish = () => { - const [ feedback, setFeedback ] = useState( '' ); + const [ hasConsent, setConsent ] = useState( true ); + const [ email, setEmail ] = useState( window.otterOnboardingData?.userEmail ); const [ isLoading, setIsLoading ] = useState( false ); - const { theme } = useSelect( select => { - const { getCurrentTheme } = select( 'core' ); - - const theme = getCurrentTheme()?.template || getCurrentTheme()?.stylesheet; + const { hasUserOptedin } = useSelect( select => { + const { get } = select( 'core/preferences' ); return { - theme + hasUserOptedin: get( 'themeisle/otter-blocks', 'onboarding-optin' ) }; - }); + }, []); + + const persistenceLayer = create(); + + const { + set, + setPersistenceLayer + } = useDispatch( 'core/preferences' ); + + setPersistenceLayer( persistenceLayer ); const onFinish = ({ redirect = 'site' }) => { const url = 'site' === redirect ? window.otterOnboardingData.rootUrl : window.otterOnboardingData.dashboardUrl; - if ( ! feedback ) { + if ( ! email || ! hasConsent ) { window.open( url, '_self' ); return; } setIsLoading( true ); - fetch( 'https://api.themeisle.com/tracking/feedback', { + fetch( 'https://api.themeisle.com/tracking/subscribe', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -46,16 +58,13 @@ const Finish = () => { 'Cache-Control': 'no-cache' }, body: JSON.stringify({ - slug: 'otter-blocks', - version, - feedback, - data: { - 'feedback-area': 'onboarding', - theme - } + slug: 'raft', // We need to see how we can make it work for any themes + site: window.otterOnboardingData.rootUrl, + email }) }).then( r => { setIsLoading( false ); + set( 'themeisle/otter-blocks', 'onboarding-optin', true ); window.open( url, '_self' ); })?.catch( () => { setIsLoading( false ); @@ -74,12 +83,23 @@ const Finish = () => {

{ __( 'Your website is ready!', 'otter-blocks' ) }

{ __( 'Thanks for using Otter to setup your theme. Otter adds a number of useful blocks and features to your site that enhance your FSE experience. We hope you will enjoy using it.', 'otter-blocks' ) }

- + { ! hasUserOptedin && ( + <> + + + + + ) }
From 3d10a10ad82c1e41316001f5be12cc548a6c1f12 Mon Sep 17 00:00:00 2001 From: Hardeep Asrani Date: Mon, 18 Dec 2023 17:41:09 +0530 Subject: [PATCH 37/37] Toggle Email field with consent checkbox --- src/onboarding/components/Finish.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/onboarding/components/Finish.js b/src/onboarding/components/Finish.js index 056fcfd25..24fb14fd9 100644 --- a/src/onboarding/components/Finish.js +++ b/src/onboarding/components/Finish.js @@ -91,13 +91,15 @@ const Finish = () => { onChange={ setConsent } /> - + { hasConsent && ( + + ) } ) }