├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── README.md ├── bin ├── build-and-deploy.sh └── build-excludes.txt ├── class.jetpack-onboarding-end-points.php ├── class.jetpack-onboarding-welcome-panel.php ├── client ├── .DS_Store ├── actions │ ├── data-actions.js │ ├── flash-actions.js │ ├── setup-progress-actions.js │ ├── site-actions.js │ └── spinner-actions.js ├── components │ ├── dashicon.jsx │ ├── flash.jsx │ ├── page │ │ ├── container.jsx │ │ ├── index.jsx │ │ └── section.jsx │ ├── skip-button.jsx │ └── steps │ │ ├── business-address.jsx │ │ ├── contact.jsx │ │ ├── get-started.jsx │ │ ├── homepage.jsx │ │ ├── jetpack-jumpstart.jsx │ │ ├── layout.jsx │ │ ├── review.jsx │ │ ├── site-title.jsx │ │ └── woocommerce.jsx ├── constants │ ├── jetpack-onboarding-constants.js │ └── jetpack-onboarding-paths.js ├── dispatcher │ └── app-dispatcher.js ├── ie-shims.js ├── jetpack-onboarding.js ├── stores │ ├── data-store.js │ ├── flash-store.js │ ├── setup-progress-store.js │ ├── site-store.js │ └── spinner-store.js ├── utils │ └── wp-ajax.js ├── vendor.js └── welcome-panel.jsx ├── font ├── Genericons-Regular.otf ├── genericons-regular-webfont.eot ├── genericons-regular-webfont.svg ├── genericons-regular-webfont.ttf └── genericons-regular-webfont.woff ├── gulpfile.js ├── img ├── feature-photon-sm.jpg ├── jpo-contact.jpg ├── jpo-layout-news.jpg ├── jpo-layout-static.jpg ├── jpo-themes.png ├── jpo-welcome.png ├── spinner-2x.gif └── stats-example-sm.png ├── jetpack-onboarding.php ├── package-lock.json ├── package.json ├── scss ├── _business-address.scss ├── _contact.scss ├── _container.scss ├── _get-started.scss ├── _homepage.scss ├── _jetpack-jumpstart.scss ├── _review.scss ├── _site-title.scss ├── _typography.scss ├── _woocommerce.scss ├── color-overrides.scss ├── mixins │ ├── _breakpoint.scss │ ├── _calypso-forms.scss │ └── _clearfix.scss └── welcome-panel.scss └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "stage-1", 5 | "react" 6 | ], 7 | "plugins": [ 8 | "transform-runtime", 9 | "add-module-exports", 10 | "transform-es3-member-expression-literals" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true, 5 | "es6": true, 6 | "mocha": true, 7 | "node": true 8 | }, 9 | "ecmaFeatures": { 10 | "jsx": true, 11 | "modules": true 12 | }, 13 | "plugins": [ 14 | "eslint-plugin-react" 15 | ], 16 | "rules": { 17 | "brace-style": [ 1, "1tbs" ], 18 | // REST API objects include underscores 19 | "camelcase": 0, 20 | "comma-dangle": 0, 21 | "comma-spacing": 1, 22 | // Allows returning early as undefined 23 | "consistent-return": 0, 24 | "dot-notation": 1, 25 | "eqeqeq": [ 2, "allow-null" ], 26 | "eol-last": 1, 27 | "indent": [ 1, "tab", { "SwitchCase": 1 } ], 28 | 'keyword-spacing': 1, 29 | "key-spacing": 1, 30 | // Most common is "Emitter", should be improved 31 | "new-cap": 1, 32 | "no-cond-assign": 2, 33 | "no-else-return": 1, 34 | "no-empty": 1, 35 | // Flux stores use switch case fallthrough 36 | "no-fallthrough": 0, 37 | "no-lonely-if": 1, 38 | "no-mixed-requires": 0, 39 | "no-mixed-spaces-and-tabs": 1, 40 | "no-multiple-empty-lines": [ 1, { max: 1 } ], 41 | "no-multi-spaces": 1, 42 | "no-nested-ternary": 1, 43 | "no-new": 1, 44 | "no-process-exit": 1, 45 | "no-shadow": 1, 46 | "no-spaced-func": 1, 47 | "no-trailing-spaces": 1, 48 | "no-underscore-dangle": 0, 49 | // Allows Chai `expect` expressions 50 | "no-unused-expressions": 0, 51 | "no-unused-vars": 1, 52 | // Teach eslint about React+JSX 53 | "react/jsx-uses-react": 1, 54 | "react/jsx-uses-vars": 1, 55 | // Allows function use before declaration 56 | "no-use-before-define": [ 2, "nofunc" ], 57 | // We split external, internal, module variables 58 | "one-var": 0, 59 | "operator-linebreak": [ 1, "after" ], 60 | "padded-blocks": [ 1, "never" ], 61 | "quote-props": [ 1, "as-needed" ], 62 | "quotes": [ 1, "single", "avoid-escape" ], 63 | "semi-spacing": 1, 64 | "space-before-blocks": [ 1, "always" ], 65 | "space-before-function-paren": [ 1, "never" ], 66 | // Our array literal index exception violates this rule 67 | "space-in-brackets": 0, 68 | "space-in-parens": [ 1, "always" ], 69 | "space-infix-ops": [ 1, { "int32Hint": false } ], 70 | // Ideal for "!" but not for "++" 71 | "space-unary-ops": 0, 72 | // Assumed by default with Babel 73 | "strict": [ 2, "never" ], 74 | "valid-jsdoc": [ 1, { "requireReturn": false } ], 75 | // Common top-of-file requires, expressions between external, interal 76 | "vars-on-top": 1, 77 | "yoda": 0 78 | } 79 | } 80 | 81 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.scssc 2 | node_modules 3 | dist 4 | css 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Jetpack Onboarding 2 | ============= 3 | 4 | Jetpack Onboarding is our attempt at exploring a better New User Experience flow when a user first sets up WordPress. 5 | 6 | It is designed to be extensible, so that hosting companies that choose to bundle it can easily modify, add, or remove any of the steps that we've included by default. 7 | 8 | Most of the code lives in the client, and uses [ReactJS](https://github.com/facebook/react) and Facebook's [Flux Dispatcher](https://github.com/facebook/flux) to manage the flow of data and UI updates. 9 | 10 | This initiative is currently being led by Daniel Walmsley [@gravityrail](http://github.com/gravityrail) and Jesse Friedman [@jessefriedman](http://github.com/jessefriedman), with design by Dan Hauk [@danhauk](https://github.com/danhauk). 11 | 12 | Previous contributors who laid the foundation are Luca Sartoni [@lucasartoni](https://github.com/lucasartoni), Dave Martin [@growthdesigner](http://github.com/growthdesigner) and Miguel Lezama [@lezama](http://github.com/lezama). 13 | 14 | Pull Requests and Issues are always welcome. :) 15 | 16 | ## Integrating 17 | 18 | This plugin publishes three hooks: 19 | - jpo_started 20 | - jpo_step_skipped 21 | - jpo_step_complete 22 | 23 | jpo_started is invoked when the user clicks the "Get Started ->" link on the front page of the wizard. The latter two "step" hooks are invoked with a string (a "slug") which names the step. 24 | 25 | Each "jpo_step_completed" step is accompanied by a data hash, which at a minimum includes an entry called "completion", which is the % completion of the wizard. For example, a step completion hash for the "design" step might look like this: 26 | 27 | ``` 28 | $data = array( 29 | 'themeId' => 'edit', 30 | 'completion' => 60 31 | ) 32 | ``` 33 | 34 | An integration might look like this: 35 | 36 | ```php 37 | 'jpo_'.$event_type, 70 | 'step' => $step_slug, 71 | 'user_id' => $current_user->ID, 72 | 'user_email' => $current_user->user_email, 73 | '_ip' => $_SERVER['REMOTE_ADDR'], 74 | 'data' => $data 75 | ); 76 | error_log("Recorded event: ".print_r($event, true)); 77 | } 78 | } 79 | 80 | add_action( 'init', array('JetpackOnboardingTracking', 'track_jpo_usage') ); 81 | } 82 | ``` 83 | 84 | ## Building 85 | 86 | ```bash 87 | cd /path/to/jetpack-onboarding 88 | npm install # install local dependencies 89 | npm run build # to build the css and javascript 90 | ``` 91 | 92 | Alternatively, `npm run watch` can be used to watch JS and SCSS files while actively developing, and `npm run build-production` can be used to build for production. 93 | 94 | If you get errors running `npm run build`, it could be `node-sass` issues (e.g. missing binaries like scandir). In that case, run: 95 | 96 | ```bash 97 | npm install node-sass 98 | ``` 99 | 100 | and try again to see if that fixes your issue. 101 | 102 | Directory structure: 103 | 104 | - client - this is where you come to edit javascript 105 | - actions - Flux actions. These are called by components, and manage server-side data updates and telling the Dispatcher that an event/update has occurred. 106 | - components - React components, written as JSX 107 | - constants - Shared JS constants 108 | - dispatcher - the Flux App Dispatcher 109 | - stores - Flux stores, which receive callbacks from the Dispatcher, modify client data state and tell subscribed components to update themselves 110 | - utils - currently just a wrapper for jQuery.ajax that handles the WP way of rendering JSON errors. 111 | - jetpack-onboarding.js - entry point for the JS client app 112 | - welcome-panel.js - entry point that configures and initialises the Welcome Panel on the dashboard. 113 | - css - edit the SCSS files in here. CSS files are generated by grunt-sass (see above) 114 | - font - just genericons 115 | - js - the generated client JS bundles. DO NOT MODIFY. "grunt" handles this for you (see above) 116 | 117 | ## Debugging 118 | 119 | If you load the dashboard with the parameter "jpo_reset=1", then all Jetpack Onboarding *AND* Jetpack data will be reset. 120 | 121 | If you enable WP_DEBUG in wp-config.php, then you'll see some additional buttons on the wizard UI for resetting wizard progress data (just the wizard progress in this case, not Jetpack itself) and showing and hiding the spinner overlay. 122 | 123 | ## Filters 124 | 125 | There's a few ways you can customise behaviour of JPO via filters. 126 | 127 | ### Skipping wizard steps 128 | 129 | You can selectively disable any step with a filter, `jpo_wizard_step_enabled_{$STEP_SLUG}`. The step slugs are listed in `jetpack-onboarding-paths.js`. 130 | 131 | e.g. to skip the title and layout (blog vs website) step: 132 | 133 | ```php 134 | add_filter( 'jpo_wizard_step_enabled_title', '__return_false' ); 135 | add_filter( 'jpo_wizard_step_enabled_is-blog', '__return_false' ); 136 | ``` 137 | 138 | These will also remove the corresponding review items from the final "review" page. 139 | 140 | ### Final step call-to-action 141 | 142 | The final step has a "call to action" on the right which by default encourages the user to enter the customizer. The following filters allow hiding or modifying this behaviour. 143 | 144 | #### Hiding the final step call to action 145 | 146 | ```php 147 | add_filter( 'jpo_review_show_cta', '__return_false' ); 148 | ``` 149 | 150 | #### Replacing the image and button text/link on final call to action 151 | 152 | ```php 153 | add_filter( 'jpo_review_cta_image', 'my_cta_image_url' ); 154 | add_filter( 'jpo_review_cta_button_text', 'my_cta_button_text' ); 155 | add_filter( 'jpo_review_cta_button_url', 'my_cta_button_url' ); 156 | 157 | function my_cta_image_url() { 158 | return '/my-images/cta-promo.png'; 159 | } 160 | 161 | function my_cta_button_text() { 162 | return 'Install our Premium Plugin'; 163 | } 164 | 165 | function my_cta_button_url() { 166 | return 'http://example.com'; 167 | } 168 | ``` 169 | 170 | ### Customizing the from tracking argument 171 | 172 | By default, when a site is connected to WordPress.com, a `from` argument gets appeneded to the connection URL which is used to track where the connection came from. Originally, this argument was simply `from=jpo`, but recently we started appending the version number to allow differentiating between versions. This looked like, `from=jpo-1.7.1`. 173 | 174 | While that has helped, we've found that it didn't all use cases. So, we are adding the `jpo_tracking_from_arg` filter which will allow hosts that use Jetpack Onboarding to fine-tune the `from` argument. 175 | 176 | Here is some example usage: 177 | 178 | ```php 179 | add_filter( 'jpo_tracking_from_arg', 'modify_jpo_from', 10, 2 ); 180 | function modify_jpo_from( $from, $version ) { 181 | return sprintf( 'jpo-%s-my-cool-host', $version ); 182 | } 183 | ``` 184 | 185 | ## Inserting the JPO wizard onto other pages 186 | 187 | By default, JPO runs in the welcome panel, but you can run it by inserting a div with the id 'jpo-welcome-panel' into any other page, like this: 188 | 189 | ```php 190 | // add assets 191 | add_action( 'admin_enqueue_scripts', array( 'Jetpack_Onboarding_WelcomePanel', 'add_wizard_assets' ) ); 192 | // add wizard HTML 193 | add_action( 'admin_notices', 'add_jpo_wizard' ); 194 | function add_jpo_wizard() { 195 | if ( get_option( Jetpack_Onboarding_EndPoints::HIDE_FOR_ALL_USERS_OPTION ) ) { 196 | return; 197 | } 198 | ?> 199 | Jetpack_Onboarding_WelcomePanel::render_widget(); 200 | base ) { 210 | // add assets 211 | add_action( 'admin_enqueue_scripts', array( 'Jetpack_Onboarding_WelcomePanel', 'add_wizard_assets' ) ); 212 | // add wizard HTML 213 | add_action( 'admin_notices', 'add_jpo_wizard' ); 214 | } 215 | } 216 | 217 | function add_jpo_wizard() { 218 | if ( get_option( Jetpack_Onboarding_EndPoints::HIDE_FOR_ALL_USERS_OPTION ) ) { 219 | return; 220 | } 221 | 222 | Jetpack_Onboarding_WelcomePanel::render_widget(); 223 | } 224 | ``` 225 | 226 | If you decide to show the wizard on another page, you may also want to disable showing the wizard on the dashboard. To do this, you can do the following: 227 | 228 | ```php 229 | add_filter( 'jetpack_onboarding_show_on_dashboard', '__return_false' ); 230 | ``` 231 | 232 | ## Styling 233 | 234 | ### Move the top of the wizard down to expose top elements in dashboard 235 | 236 | Inject this CSS: 237 | 238 | ```css 239 | #jpo-welcome-panel { 240 | top: 100px !important; 241 | } 242 | ``` 243 | 244 | ### Add a logo inside the wizard panel as a background 245 | 246 | Given a logo an image, e.g. http://example.com/wp-content/uploads/2017/MyHost_Logo.png, let's scale the logo, move it to the middle at the top, and move the content down: 247 | 248 | ```css 249 | #jpo-welcome-panel { 250 | background-image: url( http://local.wordpress.goldsounds.wpvm.io/wp-content/uploads/2017/05/MyHost_Logo.png ); 251 | background-repeat: no-repeat; 252 | background-position: 50% 30px; 253 | padding-top: 120px; 254 | background-size: 400px; 255 | } 256 | ``` 257 | -------------------------------------------------------------------------------- /bin/build-and-deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | RED='\033[0;31m' 3 | trap 'exit_build' ERR 4 | 5 | function exit_build { 6 | echo -e "${RED}Something went wrong and the build has stopped.See error above for more details." 7 | exit 1 8 | } 9 | 10 | # Currently a one-off script to push a built version to a GitHub branch or tag. 11 | # If no tag or branch is set as a param, it defaults to 'master-built' branch. 12 | 13 | JPO_GIT_DIR=$(dirname "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" ) 14 | JPO_TMP_DIR="/tmp/jpo" 15 | JPO_TMP_DIR_2="/tmp/jpo2" 16 | TARGET=${1:-master-built} 17 | 18 | cd $JPO_GIT_DIR 19 | 20 | # Make sure we don't have uncommitted changes. 21 | if [[ -n $( git status -s --porcelain ) ]]; then 22 | echo "Uncommitted changes found." 23 | echo "Please deal with them and try again clean." 24 | exit 1 25 | fi 26 | 27 | # Make sure we're trying to deploy something that exists. 28 | if [[ -z $( git branch -r | grep "$TARGET" ) && -z $( git tag | grep "$TARGET" ) ]]; then 29 | echo "Branch or Tag $TARGET not found in git repository." 30 | echo "Please try again with a valid tag or branch name." 31 | exit 1 32 | fi 33 | 34 | read -p "You are about to deploy a new production build to the $TARGET branch or tag. Are you sure? [y/N]" -n 1 -r 35 | if [[ $REPLY != "y" && $REPLY != "Y" ]] 36 | then 37 | exit 1 38 | fi 39 | echo "" 40 | 41 | echo "Building Jetpack Onboarding" 42 | 43 | npm run build-production 44 | 45 | echo "Done" 46 | 47 | # Prep a home to drop our new files in. Just make it in /tmp so we can start fresh each time. 48 | rm -rf $JPO_TMP_DIR 49 | rm -rf $JPO_TMP_DIR_2 50 | 51 | echo "Rsync'ing everything over to $JPO_TMP_DIR_2" 52 | 53 | rsync -r --exclude-from 'bin/build-excludes.txt' $JPO_GIT_DIR/* $JPO_TMP_DIR_2 54 | 55 | echo "Done!" 56 | 57 | echo "Pulling latest from $TARGET branch" 58 | git clone --depth 1 -b $TARGET --single-branch git@github.com:Automattic/jetpack-onboarding.git $JPO_TMP_DIR 59 | echo "Done!" 60 | 61 | cd $JPO_TMP_DIR 62 | 63 | # Remove all files except the .git directory, so that we get a clean copy. 64 | git rm -rfq . 65 | 66 | echo "Rsync'ing everything over remote version" 67 | rsync -r $JPO_TMP_DIR_2/* $JPO_TMP_DIR 68 | echo "Done!" 69 | 70 | echo "Finally, Committing and Pushing" 71 | git add . 72 | git commit -am 'New build' 73 | git push origin $TARGET 74 | echo "Done! Branch $TARGET has been updated." 75 | 76 | echo "Cleaning up the mess" 77 | cd $JPO_GIT_DIR 78 | rm -rf $JPO_TMP_DIR 79 | rm -rf $JPO_TMP_DIR_2 80 | echo "All clean!" 81 | -------------------------------------------------------------------------------- /bin/build-excludes.txt: -------------------------------------------------------------------------------- 1 | /.git/ 2 | /node_modules/ 3 | /bin/ 4 | /client/ 5 | /scss/ 6 | .babelrc 7 | .editorconfig 8 | .eslintrc 9 | .gitignore 10 | gulpfile.js 11 | package.json 12 | webpack.config.js 13 | -------------------------------------------------------------------------------- /class.jetpack-onboarding-end-points.php: -------------------------------------------------------------------------------- 1 | true, 68 | 'configured' => Jetpack::is_active(), 69 | 'logo_url' => plugins_url('jetpack/images/jetpack-logo.png'), 70 | 'jumpstart_modules' => array_values(self::jumpstart_modules()), 71 | 'additional_modules' => array(), 72 | 'active_modules' => array_values(Jetpack::init()->get_active_modules()) 73 | ); 74 | } else { 75 | $jetpack_config = array( 76 | 'plugin_active' => false, 77 | 'configured' => false, 78 | 'logo_url' => '', 79 | 'jumpstart_modules' => array(), 80 | 'additional_modules' => array(), 81 | 'active_modules' => array() 82 | ); 83 | } 84 | 85 | // set the jetpack step status to "completed" if jetpack is active 86 | if ( $jetpack_config['configured'] ) { 87 | $step_statuses['jetpack'] = array('completed' => true); 88 | } 89 | 90 | $step_slugs = array( 91 | 'title', 92 | 'is-blog', 93 | 'homepage', 94 | 'traffic', 95 | 'stats-monitoring', 96 | 'design', 97 | 'advanced', 98 | 'review', 99 | 'jetpack', 100 | 'contact-page', 101 | 'business-address', 102 | 'woocommerce' 103 | ); 104 | 105 | // create an assoc array of step_key => apply_filters( 'jpo_step_enabled_$slug', true ); 106 | $steps_enabled = array_combine( 107 | $step_slugs, 108 | array_map( 109 | array( 'Jetpack_Onboarding_EndPoints', 'filter_wizard_step_enabled' ), 110 | $step_slugs 111 | ) 112 | ); 113 | 114 | return array( 115 | 'base_url' => JETPACK_ONBOARDING_BASE_URL, 116 | 'site_url' => site_url(), 117 | 'nonce' => wp_create_nonce( Jetpack_Onboarding_EndPoints::AJAX_NONCE ), 118 | 'debug' => WP_DEBUG ? true : false, 119 | 'bloginfo' => array( 120 | 'name' => wp_kses_decode_entities(stripslashes(get_bloginfo('name'))), 121 | 'description' => wp_kses_decode_entities(stripslashes(get_bloginfo('description'))), 122 | 'type' => $site_type, 123 | ), 124 | 'site_actions' => array( 125 | 'set_title' => 'jpo_set_title', 126 | 'set_layout' => 'jpo_set_layout', 127 | 'set_theme' => 'jpo_set_theme', 128 | 'install_theme' => 'jpo_install_theme', 129 | 'get_popular_themes' => 'jpo_get_popular_themes', 130 | 'configure_jetpack' => 'jpo_configure_jetpack', 131 | 'add_business_address' => 'jpo_add_business_address', 132 | 'activate_jetpack_modules' => 'jpo_activate_jetpack_modules', 133 | 'deactivate_jetpack_modules' => 'jpo_deactivate_jetpack_modules', 134 | 'list_jetpack_modules' => 'jpo_list_jetpack_modules', 135 | 'reset_data' => 'jpo_reset_data', 136 | 'build_contact_page' => 'jpo_build_contact_page', 137 | 'install_woocommerce' => 'jpo_install_woocommerce' 138 | ), 139 | 'step_actions' => array( 140 | 'start' => 'jpo_started', 141 | 'disable' => 'jpo_disabled', 142 | 'close' => 'jpo_closed', 143 | 'view' => 'jpo_step_view', 144 | 'skip' => 'jpo_step_skip', 145 | 'complete' => 'jpo_step_complete' 146 | ), 147 | 'jetpack' => $jetpack_config, 148 | 'woocommerce_status' => is_plugin_active( self::WOOCOMMERCE_ID ), 149 | 'started' => $started, 150 | 'step_status' => $step_statuses, 151 | 'step_enabled' => $steps_enabled, 152 | 'steps' => array( 153 | 'layout' => self::get_layout(), 154 | 'contact_page' => $contact_page_info, 155 | 'business_address' => ( bool ) $business_address_saved, 156 | 'advanced_settings' => array( 157 | 'show_cta' => apply_filters( 'jpo_review_show_cta', true ), 158 | 'cta_image' => apply_filters( 'jpo_review_cta_image', JETPACK_ONBOARDING_BASE_URL . '/img/jpo-themes.png' ), 159 | 'cta_button_text' => apply_filters( 'jpo_review_cta_button_text', 'Customize your site' ), 160 | 'cta_button_url' => apply_filters( 'jpo_review_cta_button_url', wp_customize_url() ), 161 | 'jetpack_modules_url' => admin_url( 'admin.php?page=jetpack#/settings' ), 162 | 'jetpack_dash' => admin_url( 'admin.php?page=jetpack' ), 163 | 'widgets_url' => admin_url( 'widgets.php' ), 164 | 'themes_url' => admin_url( 'themes.php' ), 165 | 'plugins_url' => admin_url( 'plugins.php' ), 166 | 'customize_url' => wp_customize_url(), 167 | 'new_blog_post_url' => admin_url( 'post-new.php' ), 168 | 'manage_posts_url' => admin_url( 'edit.php' ), 169 | 'new_page_url' => admin_url( 'post-new.php?post_type=page' ), 170 | 'manage_pages_url' => admin_url( 'edit.php?post_type=page' ), 171 | 'woocommerce_setup_url' => admin_url( 'admin.php?page=wc-setup' ) 172 | ) 173 | ), 174 | ); 175 | } 176 | 177 | static function filter_wizard_step_enabled( $step_slug ) { 178 | return apply_filters( "jpo_wizard_step_enabled_$step_slug", true ); 179 | } 180 | 181 | static function get_layout() { 182 | $posts_url = $welcome_url = ''; 183 | if ( get_option( 'show_on_front' ) == 'page') { 184 | if ( get_option( 'page_for_posts' ) == 0 || get_option( 'page_for_posts' ) == null ) { 185 | $layout = 'website'; 186 | $welcome_url = get_edit_post_link( get_option( 'page_on_front' ) ); 187 | $posts_url = ''; 188 | } else { 189 | $layout = 'site-blog'; 190 | $welcome_url = get_edit_post_link( get_option( 'page_on_front' ) ); 191 | $posts_url = get_edit_post_link( get_option( 'page_for_posts' ) ); 192 | } 193 | } else { 194 | $layout = 'blog'; 195 | } 196 | 197 | return array( 198 | 'current' => $layout, 199 | 'welcomeEditUrl' => $welcome_url, 200 | 'postsEditUrl' => $posts_url, 201 | ); 202 | } 203 | 204 | static function default_theme_filter($theme) { 205 | return ( in_array( $theme['id'], self::$default_themes ) || get_stylesheet() == $theme['id'] ); 206 | } 207 | 208 | static function existing_theme_filter($theme) { 209 | return !wp_get_theme( $theme->slug )->exists(); 210 | } 211 | 212 | static function get_popular_themes() { 213 | // add_action( 'wp_ajax_jpo_set_theme', array( __CLASS__, 'set_theme' ) ); 214 | global $theme_field_defaults; 215 | $args = array( 216 | 'browse' => 'popular', 217 | 'per_page' => 40, 218 | 'fields' => $theme_field_defaults 219 | ); 220 | $themes = themes_api( 'query_themes', $args ); 221 | if ( is_wp_error( $themes )) { 222 | wp_send_json_error("There was an error loading themes: ".$themes->get_error_message()); 223 | die(); 224 | } 225 | $non_installed_themes = array_filter($themes->themes, array(__CLASS__, 'existing_theme_filter')); 226 | $rand_keys_to_exclude = array_rand( $non_installed_themes, ( sizeof($non_installed_themes) - self::NUM_RAND_THEMES ) ); 227 | 228 | $random_non_installed_themes = array_diff_key( $non_installed_themes, array_flip($rand_keys_to_exclude) ); 229 | 230 | 231 | 232 | // error_log(print_r($random_non_installed_themes, true)); 233 | 234 | wp_send_json_success( array_map( array(__CLASS__, 'normalize_api_theme'), array_values( $random_non_installed_themes ) ) ); 235 | } 236 | 237 | static function normalize_api_theme($theme) { 238 | return array( 239 | 'id' => $theme->slug, 240 | 'screenshot' => array($theme->screenshot_url), 241 | 'name' => $theme->name, 242 | 'author' => $theme->author, 243 | 'description' => $theme->description, 244 | 'active' => false, 245 | 'installed' => false 246 | ); 247 | } 248 | 249 | static function normalize_installed_theme($theme) { 250 | return array( 251 | 'id' => $theme['id'], 252 | 'screenshot' => array($theme['screenshot'][0]), 253 | 'name' => $theme['name'], 254 | 'author' => $theme['author'], 255 | 'description' => $theme['description'], 256 | 'active' => $theme['active'], 257 | 'installed' => true 258 | ); 259 | } 260 | 261 | static function reset_data() { 262 | check_ajax_referer( self::AJAX_NONCE, 'nonce' ); 263 | 264 | jpo_reset(); 265 | 266 | wp_send_json_success( 'deleted' ); 267 | } 268 | 269 | static function activate_jetpack_modules() { 270 | check_ajax_referer( self::AJAX_NONCE, 'nonce' ); 271 | 272 | // shamelessly copied from class.jetpack.php 273 | $modules = $_REQUEST['modules']; 274 | $modules = array_map( 'sanitize_key', $modules ); 275 | // $modules_filtered = Jetpack::init()->filter_default_modules( $modules ); 276 | 277 | foreach ( $modules as $module_slug ) { 278 | Jetpack::log( 'activate', $module_slug ); 279 | Jetpack::activate_module( $module_slug, false, false ); 280 | Jetpack::state( 'message', 'no_message' ); 281 | } 282 | 283 | //XXX TODO: determine whether this is really useful 284 | // self::set_default_publicize_config(); 285 | 286 | wp_send_json_success( $modules ); 287 | } 288 | 289 | static function deactivate_jetpack_modules() { 290 | check_ajax_referer( self::AJAX_NONCE, 'nonce' ); 291 | 292 | // shamelessly copied from class.jetpack.php 293 | $modules = $_REQUEST['modules']; 294 | $modules = array_map( 'sanitize_key', $modules ); 295 | // $modules_filtered = Jetpack::init()->filter_default_modules( $modules ); 296 | 297 | foreach ( $modules as $module_slug ) { 298 | Jetpack::log( 'deactivate', $module_slug ); 299 | Jetpack::deactivate_module( $module_slug ); 300 | Jetpack::state( 'message', 'module_deactivated' ); 301 | } 302 | 303 | wp_send_json_success( $modules ); 304 | } 305 | 306 | static function list_jetpack_modules() { 307 | check_ajax_referer( self::AJAX_NONCE, 'nonce' ); 308 | $modules = Jetpack_Admin::init()->get_modules(); 309 | 310 | $module_info = array(); 311 | foreach ( $modules as $module => $value ) { 312 | $module_info[] = array( 313 | 'slug' => $value['module'], 314 | 'name' => $value['name'], 315 | 'description' => $value['jumpstart_desc'] ? $value['jumpstart_desc'] : $value['description'], 316 | 'configure_url' => $value['configurable'] ? $value['configure_url'] : null, 317 | ); 318 | } 319 | 320 | wp_send_json_success( array_values($module_info) ); 321 | } 322 | 323 | static function jumpstart_modules() { 324 | $modules = Jetpack_Admin::init()->get_modules(); 325 | 326 | $module_info = array(); 327 | foreach ( $modules as $module => $value ) { 328 | if ( in_array( 'Jumpstart', $value['feature'] ) ) { 329 | $module_info[] = array( 330 | 'slug' => $value['module'], 331 | 'name' => $value['name'], 332 | 'description' => $value['jumpstart_desc'], 333 | 'configure_url' => $value['configurable'] ? $value['configure_url'] : null, 334 | ); 335 | } 336 | } 337 | return $module_info; 338 | } 339 | 340 | // shamelessly copied from class.jetpack.php 341 | static function set_default_publicize_config() { 342 | // Set the default sharing buttons and set to display on posts if none have been set. 343 | $sharing_services = get_option( 'sharing-services' ); 344 | $sharing_options = get_option( 'sharing-options' ); 345 | if ( empty( $sharing_services['visible'] ) ) { 346 | // Default buttons to set 347 | $visible = array( 348 | 'twitter', 349 | 'facebook', 350 | 'google-plus-1', 351 | ); 352 | $hidden = array(); 353 | 354 | // Set some sharing settings 355 | $sharing = new Sharing_Service(); 356 | $sharing_options['global'] = array( 357 | 'button_style' => 'icon', 358 | 'sharing_label' => $sharing->default_sharing_label, 359 | 'open_links' => 'same', 360 | 'show' => array( 'post' ), 361 | 'custom' => isset( $sharing_options['global']['custom'] ) ? $sharing_options['global']['custom'] : array() 362 | ); 363 | 364 | update_option( 'sharing-options', $sharing_options ); 365 | update_option( 'sharing-services', array( 'visible' => $visible, 'hidden' => $hidden ) ); 366 | } 367 | } 368 | 369 | static function started() { 370 | check_ajax_referer( self::AJAX_NONCE, 'nonce' ); 371 | update_option( self::STARTED_KEY, true ); 372 | do_action('jpo_started', $_REQUEST['siteType']); 373 | update_option( self::SITE_TYPE, $_REQUEST['siteType'] ); 374 | wp_send_json_success( 'true' ); 375 | } 376 | 377 | // These next two functions are the same, 378 | // but the first == "NO" on opening screen, 379 | // whereas the second happens if you close the metabox 380 | static function disabled() { 381 | check_ajax_referer( self::AJAX_NONCE, 'nonce' ); 382 | self::hide_dashboard_widget(); 383 | do_action('jpo_disabled'); 384 | wp_send_json_success( 'true' ); 385 | } 386 | 387 | static function closed() { 388 | check_ajax_referer( self::AJAX_NONCE, 'nonce' ); 389 | self::hide_dashboard_widget(); 390 | wp_send_json_success( 'true' ); 391 | } 392 | 393 | static function hide_dashboard_widget() { 394 | $setting = get_user_option( get_current_user_id(), "metaboxhidden_dashboard" ); 395 | 396 | if ( !$setting || !is_array( $setting ) ) { 397 | $setting = array(); 398 | } 399 | 400 | if ( ! in_array( Jetpack_Onboarding_WelcomePanel::DASHBOARD_WIDGET_ID, $setting ) ) { 401 | $setting[] = Jetpack_Onboarding_WelcomePanel::DASHBOARD_WIDGET_ID; 402 | update_user_option( get_current_user_id(), "metaboxhidden_dashboard", $setting, true); 403 | } 404 | 405 | // hide for all users 406 | update_option( self::HIDE_FOR_ALL_USERS_OPTION, 1 ); 407 | } 408 | 409 | static function step_view() { 410 | check_ajax_referer( self::AJAX_NONCE, 'nonce' ); 411 | do_action('jpo_step_viewed', $_REQUEST['step']); 412 | wp_send_json_success( 'true' ); 413 | } 414 | 415 | static function step_skip() { 416 | check_ajax_referer( self::AJAX_NONCE, 'nonce' ); 417 | $result = self::update_step_status($_REQUEST['step'], 'skipped', true); 418 | do_action('jpo_step_skipped', $_REQUEST['step']); 419 | wp_send_json_success( $result ); 420 | } 421 | 422 | static function step_complete() { 423 | check_ajax_referer( self::AJAX_NONCE, 'nonce' ); 424 | self::update_step_status($_REQUEST['step'], 'completed', true); 425 | $result = self::update_step_status($_REQUEST['step'], 'skipped', false); 426 | 427 | if ( array_key_exists('data', $_REQUEST) ) { 428 | $data = $_REQUEST['data']; 429 | } else { 430 | $data = null; 431 | } 432 | 433 | do_action('jpo_step_complete', $_REQUEST['step'], $data); 434 | wp_send_json_success( $result ); 435 | } 436 | 437 | static function update_step_status($step, $field, $value) { 438 | $step_statuses = get_option( self::STEP_STATUS_KEY, array() ); 439 | 440 | if( ! array_key_exists( $step, $step_statuses ) ) { 441 | $step_statuses[$step] = array(); 442 | } 443 | 444 | $step_statuses[$step][$field] = $value; 445 | update_option( self::STEP_STATUS_KEY, $step_statuses ); 446 | return $step_statuses; 447 | } 448 | 449 | static function set_title() { 450 | check_ajax_referer( self::AJAX_NONCE, 'nonce' ); 451 | 452 | $title = wp_unslash( $_REQUEST['title'] ); 453 | $description = wp_unslash( $_REQUEST['description'] ); 454 | 455 | // User input is sanitized before comparing to ensure an accurate differential. 456 | // If they are not equal, the option is updated. 457 | $updated_title = get_option( 'blogname' ) === sanitize_option( 'blogname', $title ) 458 | || update_option( 'blogname', $title ); 459 | $updated_description = get_option( 'blogdescription' ) === sanitize_option( 'blogdescription', $description ) 460 | || update_option( 'blogdescription', $description ); 461 | 462 | if ( $updated_title && $updated_description ) { 463 | wp_send_json_success( $title ); 464 | } else { 465 | wp_send_json_error(); 466 | } 467 | } 468 | 469 | static function build_contact_page() { 470 | check_ajax_referer( self::AJAX_NONCE, 'nonce' ); 471 | 472 | $jpo_contact_us_page = array( 473 | 'post_title' => 'Contact Us', 474 | 'post_content' => 'Hi There, 475 | We are looking forward to hearing from you. Please feel free to get in touch via the form below, we will get back to you as soon as possible. 476 | 477 | A Great Company Name 478 | 123 Main St, 479 | Warwick, RI 02889 480 | 718.555.0062 481 | 482 | 483 | [contact-form][contact-field label=\'Name\' type=\'name\' required=\'1\'/][contact-field label=\'Email\' type=\'email\' required=\'1\'/][contact-field label=\'Comment\' type=\'textarea\' required=\'1\'/][/contact-form]', 484 | 'post_status' => 'publish', 485 | 'post_type' => 'page' 486 | ); 487 | 488 | // Insert the page into the database 489 | $page_id = wp_insert_post( $jpo_contact_us_page ); 490 | 491 | if ( 0 !== $page_id ) { 492 | update_option( self::CONTACTPAGE_ID_KEY, $page_id ); 493 | do_action('jpo_contact_page_built'); 494 | wp_send_json_success( self::contact_page_to_json( $page_id ) ); 495 | } else { 496 | wp_send_json_error( $page_id ); 497 | } 498 | } 499 | 500 | static function contact_page_to_json( $page_id ) { 501 | $contact_page = get_post($page_id); 502 | 503 | return array( 504 | 'url' => $contact_page->guid, 505 | 'editUrl' => get_edit_post_link( $page_id ), 506 | 'post_title' => $contact_page->post_title, 507 | 'post_content' => $contact_page->post_content 508 | ); 509 | } 510 | 511 | static function set_layout() { 512 | check_ajax_referer( self::AJAX_NONCE, 'nonce' ); 513 | $layout = esc_html( $_REQUEST['layout'] ); 514 | 515 | if ( $layout == 'website' ) { 516 | self::set_layout_to_website(); 517 | } elseif ( $layout == 'site-blog' ) { 518 | self::set_layout_to_site_with_blog(); 519 | } elseif ( $layout == 'blog') { 520 | self::set_layout_to_blog(); 521 | } else { 522 | wp_send_json_error('Unknown layout type: '.$layout); 523 | } 524 | } 525 | 526 | static function set_theme() { 527 | check_ajax_referer( self::AJAX_NONCE, 'nonce' ); 528 | $theme_id = $_REQUEST['themeId']; 529 | $theme = wp_get_theme( $theme_id ); 530 | 531 | // try to install the theme if it doesn't exist 532 | if ( ! $theme->exists() ) { 533 | wp_send_json_error('Theme does not exist: '.$theme_id); 534 | die(); 535 | } 536 | 537 | if ( ! $theme->is_allowed() ) { 538 | wp_send_json_error('Action not permitted for '.$theme_id); 539 | die(); 540 | } 541 | 542 | switch_theme( $theme->get_stylesheet() ); 543 | wp_send_json_success( $theme_id ); 544 | } 545 | 546 | static function install_theme() { 547 | check_ajax_referer( self::AJAX_NONCE, 'nonce' ); 548 | $theme_id = $_REQUEST['themeId']; 549 | $theme = wp_get_theme( $theme_id ); 550 | 551 | if ( ! $theme->exists() ) { 552 | include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' ); 553 | include_once( ABSPATH . 'wp-admin/includes/theme-install.php' ); 554 | 555 | $theme_info = themes_api( 'theme_information', array( 556 | 'slug' => $theme_id, 557 | 'fields' => array( 'sections' => false, 'tags' => false ) 558 | ) ); 559 | 560 | error_log(print_r($theme_info, true)); 561 | 562 | if ( is_wp_error( $theme_info ) ) { 563 | wp_send_json_error('Could not look up theme '.$theme_id.': '.$theme_info->get_error_message()); 564 | die(); 565 | } else { 566 | $upgrader = new Theme_Upgrader( new Automatic_Upgrader_Skin() ); 567 | $install_response = $upgrader->install( $theme_info->download_link ); 568 | 569 | if( is_wp_error($install_response) ) { 570 | wp_send_json_error('Could not install theme: '.$install_response->get_error_message()); 571 | die(); 572 | } elseif ( ! $install_response ) { 573 | wp_send_json_error('Could not install theme (unspecified server error)'); 574 | die(); 575 | } 576 | } 577 | } 578 | 579 | wp_send_json_success( $theme_id ); 580 | } 581 | 582 | static function install_plugin( $slug ) { 583 | if ( is_multisite() && ! current_user_can( 'manage_network' ) ) { 584 | return new WP_Error( 'not_allowed', 'You are not allowed to install plugins on this site.' ); 585 | } 586 | include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; 587 | include_once ABSPATH . 'wp-admin/includes/file.php'; 588 | $upgrader = new Plugin_Upgrader( new Automatic_Upgrader_Skin() ); 589 | $zip_url = "https://downloads.wordpress.org/plugin/{$slug}.latest-stable.zip"; 590 | $result = $upgrader->install( $zip_url ); 591 | if ( is_wp_error( $result ) || ! $result ) { 592 | return new WP_Error( 'install_error', 'Could not install plugin ' . $slug ); 593 | } 594 | return true; 595 | } 596 | 597 | static function install_woocommerce() { 598 | check_ajax_referer( self::AJAX_NONCE, 'nonce' ); 599 | $result = true; 600 | if ( ! array_key_exists( self::WOOCOMMERCE_ID, get_plugins() ) ) { 601 | $installed = self::install_plugin( self::WOOCOMMERCE_SLUG ); 602 | if ( is_wp_error( $installed ) ) { 603 | $result = $installed; 604 | } else { 605 | $result = activate_plugin( self::WOOCOMMERCE_ID ); 606 | } 607 | } else if ( ! is_plugin_active( self::WOOCOMMERCE_ID ) ) { 608 | $result = activate_plugin( self::WOOCOMMERCE_ID ); 609 | } 610 | if ( is_wp_error( $result ) ) { 611 | wp_send_json_error( 'Could not install WooCommerce: ' . $result->get_error_message() ); 612 | } else { 613 | wp_send_json_success(); 614 | } 615 | } 616 | 617 | static function have_contact_info_widget( $sidebar ) { 618 | $sidebars_widgets = get_option( 'sidebars_widgets', array() ); 619 | 620 | if ( ! isset( $sidebars_widgets[ $sidebar ] ) ) { 621 | return false; 622 | } 623 | 624 | foreach ( $sidebars_widgets[ $sidebar ] as $widget ) { 625 | if ( strpos( $widget, 'widget_contact_info' ) !== false ) { 626 | return true; 627 | } 628 | } 629 | 630 | return false; 631 | } 632 | 633 | static function insert_widget_in_sidebar( $widget_id, $widget_options, $sidebar ) { 634 | // Retrieve sidebars, widgets and their instances 635 | $sidebars_widgets = get_option( 'sidebars_widgets', array() ); 636 | $widget_instances = get_option( 'widget_' . $widget_id, array() ); 637 | 638 | // Retrieve the key of the next widget instance 639 | $numeric_keys = array_filter( array_keys( $widget_instances ), 'is_int' ); 640 | $next_key = $numeric_keys ? max( $numeric_keys ) + 1 : 2; 641 | 642 | // Add this widget to the sidebar 643 | if ( ! isset( $sidebars_widgets[ $sidebar ] ) ) { 644 | $sidebars_widgets[ $sidebar ] = array(); 645 | } 646 | $sidebars_widgets[ $sidebar ][] = $widget_id . '-' . $next_key; 647 | 648 | // Add the new widget instance 649 | $widget_instances[ $next_key ] = $widget_options; 650 | 651 | // Store updated sidebars, widgets and their instances 652 | update_option( 'sidebars_widgets', $sidebars_widgets ); 653 | update_option( 'widget_' . $widget_id, $widget_instances ); 654 | } 655 | 656 | static function update_widget_in_sidebar( $widget_id, $widget_options, $sidebar ) { 657 | // Retrieve sidebars, widgets and their instances 658 | $sidebars_widgets = get_option( 'sidebars_widgets', array() ); 659 | $widget_instances = get_option( 'widget_' . $widget_id, array() ); 660 | 661 | // Retrieve index of first widget instance in that sidebar 662 | $widget_key = false; 663 | foreach ( $sidebars_widgets[ $sidebar ] as $widget ) { 664 | if ( strpos( $widget, 'widget_contact_info' ) !== false ) { 665 | $widget_key = absint( str_replace( 'widget_contact_info-', '', $widget ) ); 666 | break; 667 | } 668 | } 669 | 670 | if ( ! $widget_key ) { 671 | return; 672 | } 673 | 674 | // Update the widget instance with the new data 675 | $widget_instances[ $widget_key ] = array_merge( $widget_instances[ $widget_key ], $widget_options ); 676 | 677 | // Store updated widget instances 678 | update_option( 'widget_' . $widget_id, $widget_instances ); 679 | } 680 | 681 | static function get_first_sidebar() { 682 | $active_sidebars = get_option( 'sidebars_widgets', array() ); 683 | $excluded_keys = array( 684 | 'wp_inactive_widgets', 685 | 'array_version', 686 | ); 687 | 688 | foreach ( $excluded_keys as $key ) { 689 | if ( isset( $active_sidebars[ $key ] ) ) { 690 | unset( $active_sidebars[ $key ] ); 691 | } 692 | } 693 | 694 | if ( empty( $active_sidebars ) ) { 695 | return false; 696 | } 697 | 698 | return array_shift( array_keys( $active_sidebars ) ); 699 | } 700 | 701 | static function add_business_address() { 702 | check_ajax_referer( self::AJAX_NONCE, 'nonce' ); 703 | 704 | $first_sidebar = self::get_first_sidebar(); 705 | if ( $first_sidebar ) { 706 | $title = wp_unslash( $_REQUEST['business_name'] ); 707 | $address = wp_unslash( 708 | $_REQUEST['business_address_1'] . ' ' . 709 | $_REQUEST['business_address_2'] . ' ' . 710 | $_REQUEST['business_city'] . ' ' . 711 | $_REQUEST['business_state'] . ' ' . 712 | $_REQUEST['business_zip'] 713 | ); 714 | $widget_options = array( 715 | 'title' => $title, 716 | 'address' => $address, 717 | 'phone' => '', 718 | 'hours' => '', 719 | 'showmap' => false 720 | ); 721 | 722 | if ( ! self::have_contact_info_widget( $first_sidebar ) ) { 723 | self::insert_widget_in_sidebar( 'widget_contact_info', $widget_options, $first_sidebar ); 724 | } else { 725 | self::update_widget_in_sidebar( 'widget_contact_info', $widget_options, $first_sidebar ); 726 | } 727 | 728 | update_option( self::BUSINESS_ADDRESS_SAVED_KEY, 1 ); 729 | wp_send_json_success( array( 'updated' => true ) ); 730 | die(); 731 | } 732 | 733 | wp_send_json_success( array( 'updated' => false ) ); 734 | } 735 | 736 | // try to activate the plugin if necessary and kick off the jetpack connection flow 737 | // in a single action (possibly in a dialog / iframe / something?) 738 | static function configure_jetpack() { 739 | check_ajax_referer( self::AJAX_NONCE, 'nonce' ); 740 | $return_to_step = $_REQUEST['return_to_step']; 741 | 742 | if ( ! is_plugin_active('jetpack') ) { 743 | activate_plugin('jetpack'); 744 | } 745 | 746 | if ( ! class_exists('Jetpack') ) { 747 | wp_send_json_error('There was a problem activating Jetpack'); 748 | die(); 749 | } 750 | 751 | if ( ! Jetpack::is_active() ) { 752 | 753 | if ( ! Jetpack_Options::get_option( 'blog_token' ) || ! Jetpack_Options::get_option( 'id' ) ) { 754 | $result = Jetpack::try_registration(); 755 | if ( is_wp_error( $result ) ) { 756 | $error = $result->get_error_code(); 757 | $message = $result->get_error_message(); 758 | wp_send_json_error($message.' (code: '.$error.')'); 759 | die(); 760 | } 761 | } 762 | 763 | if ( class_exists( 'Jetpack_Landing_Page' ) ) { 764 | $jp_landing_page = new Jetpack_Landing_Page(); 765 | $jp_landing_page->add_actions(); 766 | } 767 | 768 | // redirect to activate link 769 | $connect_url = Jetpack::init()->build_connect_url( true, admin_url('index.php#welcome/steps/'.$return_to_step) ); 770 | 771 | if ( JETPACK_STEP_AUTO_REDIRECT ) { 772 | $connect_url = add_query_arg( 'src', JETPACK_STEP_AUTO_REDIRECT_SRC, $connect_url ); 773 | } 774 | 775 | $connect_url = add_query_arg( 'host', JETPACK_ONBOARDING_VENDOR_CODE, $connect_url ); 776 | $connect_url = add_query_arg( 'product', JETPACK_ONBOARDING_PRODUCT_CODE, $connect_url ); 777 | 778 | $from_arg = apply_filters( 'jpo_tracking_from_arg', sprintf( 'jpo-%s', self::VERSION ), self::VERSION ); 779 | $connect_url = add_query_arg( 'from', $from_arg, $connect_url ); 780 | 781 | wp_send_json_success( array('next' => $connect_url) ); 782 | } else { 783 | wp_send_json_success( 'already_active' ); 784 | } 785 | 786 | 787 | } 788 | 789 | static function set_layout_to_website() { 790 | // no posts page for this layout 791 | update_option( 'page_for_posts', null ); 792 | 793 | $page_on_front = self::set_front_page_to_page(); 794 | 795 | wp_send_json_success( array( 796 | 'current' => 'website', 797 | 'welcome' => get_edit_post_link( $page_on_front ), 798 | 'posts' => '', 799 | ) ); 800 | die(); 801 | } 802 | 803 | static function set_layout_to_site_with_blog() { 804 | $page_on_front = self::set_front_page_to_page(); 805 | 806 | $blog_page = get_page_by_path('blog'); 807 | 808 | if ( $blog_page != null ) { 809 | $page_id = $blog_page->ID; 810 | } else { 811 | // create page 812 | $page = array( 813 | 'post_type' => 'page', 814 | 'post_title' => 'Blog', 815 | 'post_name' => 'blog', 816 | 'post_content' => '', 817 | 'post_status' => 'publish', 818 | 'comment_status' => 'open' 819 | ); 820 | 821 | $page_id = wp_insert_post( $page ); 822 | } 823 | 824 | if ( $page_id == 0 ) { 825 | wp_send_json_error(); 826 | die(); 827 | } 828 | 829 | update_option( 'page_for_posts', $page_id ); 830 | wp_send_json_success( array( 831 | 'current' => 'site-blog', 832 | 'welcome' => get_edit_post_link( $page_on_front ), 833 | 'posts' => get_edit_post_link( $page_id ), 834 | ) ); 835 | } 836 | 837 | static function set_layout_to_blog() { 838 | if ( get_option( 'show_on_front' ) == 'page' ) { 839 | update_option( 'show_on_front', 'posts' ); 840 | } 841 | wp_send_json_success( array( 842 | 'current' => 'blog', 843 | 'welcome' => '', 844 | 'posts' => '', 845 | ) ); 846 | } 847 | 848 | static function set_front_page_to_page() { 849 | // ensure that front page is a static page 850 | if ( get_option( 'show_on_front' ) == 'posts' ) { 851 | update_option( 'show_on_front', 'page' ); 852 | } 853 | 854 | // if no specific front page already set, find first or create 855 | $existing_front_page = get_option( 'page_on_front' ) && 856 | ( get_option( 'page_on_front' ) != 0 ) && 857 | get_page( get_option( 'page_on_front' ) ); 858 | 859 | if ( $existing_front_page ) { 860 | return $existing_front_page; 861 | } else { 862 | // Always create a new page 863 | $page = array( 864 | 'post_type' => 'page', 865 | 'post_title' => 'Home page', 866 | 'post_name' => 'home', 867 | 'post_content' => "This is your front page. Click the 'edit' link to change the contents", 868 | 'post_status' => 'publish', 869 | 'comment_status' => 'closed' 870 | ); 871 | 872 | $page_id = wp_insert_post( $page ); 873 | 874 | if ( $page_id != 0 ) { 875 | update_option( 'page_on_front', $page_id ); 876 | return $page_id; 877 | } 878 | 879 | wp_send_json_error(); 880 | die(); 881 | } 882 | } 883 | } 884 | -------------------------------------------------------------------------------- /class.jetpack-onboarding-welcome-panel.php: -------------------------------------------------------------------------------- 1 | $dashboard[self::DASHBOARD_WIDGET_ID] ); 51 | 52 | $wp_meta_boxes['dashboard']['normal']['core'] = array_merge( $ours, $dashboard ); 53 | } 54 | 55 | static function add_wizard_assets() { 56 | // Add assets 57 | global $wp_scripts; 58 | 59 | //Shared libs, e.g. React, ReactDOM 60 | wp_register_script( 'jetpack-onboarding-vendor', plugins_url( 'dist/vendor.bundle.js', __FILE__ ), array()); 61 | wp_enqueue_script( 'jetpack-onboarding-vendor' ); 62 | 63 | //IE-only shims 64 | wp_register_script( 'ie-shims', plugins_url( 'dist/ie-shims.js', __FILE__ ), array( 'jetpack-onboarding-vendor' )); 65 | $wp_scripts->add_data( 'ie-shims', 'conditional', 'lt IE 9' ); 66 | 67 | //Core JS app 68 | wp_register_script( 'jetpack-onboarding', plugins_url( 'dist/jetpack-onboarding.js', __FILE__ ), array( 'jquery', 'underscore', 'wp-pointer', 'ie-shims', 'jetpack-onboarding-vendor' ) ); 69 | wp_localize_script( 'jetpack-onboarding', 'JPS', Jetpack_Onboarding_EndPoints::js_vars() ); 70 | wp_enqueue_script( 'jetpack-onboarding' ); 71 | 72 | // CSS 73 | wp_enqueue_style( 'jetpack-onboarding-components', plugins_url( 'dist/jetpack-onboarding.css', __FILE__ ), array( 'wp-admin' ) ); 74 | wp_enqueue_style( 'jetpack-onboarding-panel', plugins_url( 'css/welcome-panel.css', __FILE__ ), array( 'wp-admin', 'wp-pointer', 'dashicons' ) ); 75 | wp_enqueue_style( 'ie8' ); 76 | } 77 | 78 | static function render_widget() { 79 | if ( false === get_option( Jetpack_Onboarding_EndPoints::FIRSTRUN_KEY, false ) ) { 80 | update_option( Jetpack_Onboarding_EndPoints::FIRSTRUN_KEY, true ); 81 | do_action( Jetpack_Onboarding_EndPoints::FIRSTRUN_KEY ); 82 | } 83 | 84 | echo "
Loading Welcome Wizard
"; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /client/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/jetpack-onboarding/06c6c51417b95d7b766ee5aad762769b9c208571/client/.DS_Store -------------------------------------------------------------------------------- /client/actions/data-actions.js: -------------------------------------------------------------------------------- 1 | var AppDispatcher = require('../dispatcher/app-dispatcher'), 2 | JPSConstants = require('../constants/jetpack-onboarding-constants'); 3 | 4 | var DataActions = { 5 | requestStarted: function() { 6 | AppDispatcher.dispatch({ 7 | actionType: JPSConstants.SAVE_STARTED 8 | }); 9 | }, 10 | 11 | requestFinished: function() { 12 | AppDispatcher.dispatch({ 13 | actionType: JPSConstants.SAVE_FINISHED 14 | }); 15 | } 16 | }; 17 | 18 | module.exports = DataActions; -------------------------------------------------------------------------------- /client/actions/flash-actions.js: -------------------------------------------------------------------------------- 1 | var AppDispatcher = require('../dispatcher/app-dispatcher'), 2 | JPSConstants = require('../constants/jetpack-onboarding-constants'); 3 | 4 | var FlashActions = { 5 | notice: function(msg) { 6 | AppDispatcher.dispatch({ 7 | actionType: JPSConstants.SET_FLASH, 8 | message: msg, 9 | severity: JPSConstants.FLASH_SEVERITY_NOTICE 10 | }); 11 | }, 12 | 13 | error: function(msg) { 14 | AppDispatcher.dispatch({ 15 | actionType: JPSConstants.SET_FLASH, 16 | message: msg, 17 | severity: JPSConstants.FLASH_SEVERITY_ERROR 18 | }); 19 | }, 20 | 21 | unset: function() { 22 | AppDispatcher.dispatch({ 23 | actionType: JPSConstants.UNSET_FLASH 24 | }); 25 | } 26 | }; 27 | 28 | module.exports = FlashActions; -------------------------------------------------------------------------------- /client/actions/setup-progress-actions.js: -------------------------------------------------------------------------------- 1 | var AppDispatcher = require('../dispatcher/app-dispatcher'), 2 | JPSConstants = require('../constants/jetpack-onboarding-constants'), 3 | Paths = require('../constants/jetpack-onboarding-paths'), 4 | FlashActions = require('./flash-actions'), 5 | SiteActions = require('./site-actions'), 6 | WPAjax = require('../utils/wp-ajax'), 7 | SpinnerActions = require('./spinner-actions'), 8 | SetupProgressStore = require('stores/setup-progress-store'), 9 | SiteStore = require('stores/site-store'); 10 | 11 | var SetupProgressActions = { 12 | resetData: function() { 13 | WPAjax. 14 | post(JPS.site_actions.reset_data). 15 | fail(function(msg) { 16 | FlashActions.error("Failed to save data: " + msg); 17 | }); 18 | AppDispatcher.dispatch({ 19 | actionType: JPSConstants.RESET_DATA 20 | }); 21 | }, 22 | 23 | completeStepNoRecord: function( slug ) { 24 | // Sometimes we want to mark a step as complete without recording the completion in our tracking data. 25 | var step = SetupProgressStore.getStepFromSlug(slug); 26 | AppDispatcher.dispatch({ 27 | actionType: JPSConstants.STEP_COMPLETE, 28 | slug: slug 29 | }); 30 | }, 31 | 32 | completeStep: function(slug, meta) { 33 | var step = SetupProgressStore.getStepFromSlug(slug); 34 | AppDispatcher.dispatch({ 35 | actionType: JPSConstants.STEP_COMPLETE, 36 | slug: slug 37 | }); 38 | 39 | // NOTE: this needs to come after the dispatch, so that the completion % 40 | // is already updated and can be included in the metadata 41 | return this._recordStepComplete(step, meta); 42 | }, 43 | 44 | completeAndNextStep: function(slug, meta) { 45 | this.completeStep(slug, meta).always(function() { 46 | // getCurrentStep _should_ return the correct step slug for the 'next' step here... 47 | // this needs to be in the callback because otherwise there's a chance 48 | // that COMPLETE could be registered in analytics after VIEWED 49 | this._recordStepViewed( SetupProgressStore.getCurrentStep() ); 50 | }.bind(this)); 51 | 52 | AppDispatcher.dispatch({ 53 | actionType: JPSConstants.STEP_NEXT 54 | }); 55 | }, 56 | 57 | // mark current step as skipped and move on 58 | skipStep: function() { 59 | FlashActions.unset(); 60 | 61 | var step = SetupProgressStore.getCurrentStep(); 62 | 63 | if (!step.skipped) { 64 | this._recordStepSkipped( step ); 65 | } 66 | 67 | AppDispatcher.dispatch({ 68 | actionType: JPSConstants.STEP_SKIP 69 | }); 70 | }, 71 | 72 | setCurrentStep: function( stepSlug ) { 73 | FlashActions.unset(); 74 | AppDispatcher.dispatch({ 75 | actionType: JPSConstants.STEP_SELECT, 76 | slug: stepSlug 77 | }); 78 | this._recordStepViewed( { slug: stepSlug } ); 79 | }, 80 | 81 | getStarted: function( siteType ) { 82 | WPAjax. 83 | post(JPS.step_actions.start, { siteType: siteType }). 84 | fail(function(msg) { 85 | FlashActions.error(msg); 86 | }); 87 | 88 | 89 | SiteActions.setType( siteType ); 90 | 91 | AppDispatcher.dispatch({ 92 | actionType: JPSConstants.STEP_GET_STARTED 93 | }); 94 | }, 95 | 96 | closeJPO: function() { 97 | SpinnerActions.show(""); 98 | WPAjax. 99 | post(JPS.step_actions.close). 100 | fail(function(msg) { 101 | SpinnerActions.hide(); 102 | FlashActions.error(msg); 103 | }). 104 | then(function() { 105 | window.location.reload(); 106 | }); 107 | }, 108 | 109 | disableJPO: function() { 110 | SpinnerActions.show(""); 111 | WPAjax. 112 | post(JPS.step_actions.disable). 113 | fail(function(msg) { 114 | SpinnerActions.hide(); 115 | FlashActions.error(msg); 116 | }). 117 | always(function() { 118 | window.location.reload(); 119 | }); 120 | }, 121 | 122 | // moves on to the next step, but doesn't mark it as "skipped" 123 | selectNextStep: function() { 124 | FlashActions.unset(); 125 | AppDispatcher.dispatch({ 126 | actionType: JPSConstants.STEP_NEXT 127 | }); 128 | this._recordStepViewed( SetupProgressStore.getCurrentStep() ); 129 | }, 130 | 131 | submitTitleStep: function( title, description ) { 132 | SiteActions.saveTitleAndDescription( title, description ); 133 | this.completeAndNextStep(Paths.SITE_TITLE_STEP_SLUG); 134 | }, 135 | 136 | submitBusinessAddress: function( businessAddress ) { 137 | SiteActions.saveBusinessAddress( businessAddress ); 138 | this.completeStep(Paths.BUSINESS_ADDRESS_SLUG); 139 | this.setCurrentStep( Paths.WOOCOMMERCE_SLUG ); 140 | }, 141 | 142 | submitLayoutStep: function( layout ) { 143 | SiteActions.setLayout( layout ).done( function() { 144 | var step = SetupProgressStore.getStepFromSlug( Paths.IS_BLOG_STEP_SLUG ); 145 | if ( ! step.completed ) { 146 | this.completeStep( Paths.IS_BLOG_STEP_SLUG ); 147 | } 148 | this.completeAndNextStep( Paths.HOMEPAGE_STEP_SLUG ); 149 | }.bind( this ) ); 150 | }, 151 | 152 | confirmHomepageStep: function( layout ) { 153 | this.completeStep( Paths.IS_BLOG_STEP_SLUG ); 154 | this.setCurrentStep( Paths.HOMEPAGE_STEP_SLUG ); 155 | }, 156 | 157 | createContactPage: function(contactPage) { 158 | SiteActions.createContactUsPage(contactPage); 159 | this.completeStep(Paths.CONTACT_PAGE_STEP_SLUG); 160 | this.selectNextStep(); 161 | }, 162 | 163 | skipContactPageBuild: function() { 164 | this.completeAndNextStep(Paths.CONTACT_PAGE_STEP_SLUG); 165 | }, 166 | 167 | submitJetpackJumpstart: function() { 168 | SiteActions.enableJumpstart().done(function() { 169 | this.completeStep(Paths.JETPACK_MODULES_STEP_SLUG); 170 | }.bind(this)); 171 | }, 172 | 173 | setActiveTheme: function(theme) { 174 | SiteActions.setActiveTheme(theme).done(function() { 175 | this.completeStep(Paths.DESIGN_STEP_SLUG, { 176 | themeId: theme.id 177 | }); 178 | }.bind(this)); 179 | }, 180 | 181 | saveDesignStep: function() { 182 | this.completeAndNextStep(Paths.DESIGN_STEP_SLUG, { 183 | themeId: SiteStore.getActiveThemeId() 184 | }); 185 | }, 186 | 187 | _recordStepViewed: function( step ) { 188 | // record analytics to say we viewed the next step 189 | return WPAjax. 190 | post( JPS.step_actions.view, { 191 | step: step.slug 192 | }, { 193 | quiet: true 194 | } ); 195 | }, 196 | 197 | _recordStepComplete: function( step, meta ) { 198 | if (typeof(meta) === 'undefined') { 199 | meta = {}; 200 | } 201 | 202 | meta.completion = SetupProgressStore.getProgressPercent(); 203 | 204 | return WPAjax. 205 | post(JPS.step_actions.complete, { 206 | step: step.slug, 207 | data: meta 208 | }). 209 | fail(function(msg) { 210 | FlashActions.error(msg); 211 | }); 212 | }, 213 | 214 | _recordStepSkipped: function( step ) { 215 | return WPAjax. 216 | post(JPS.step_actions.skip, { 217 | step: step.slug 218 | }). 219 | fail(function(msg) { 220 | FlashActions.error(msg); 221 | }); 222 | } 223 | }; 224 | 225 | module.exports = SetupProgressActions; -------------------------------------------------------------------------------- /client/actions/site-actions.js: -------------------------------------------------------------------------------- 1 | var AppDispatcher = require('../dispatcher/app-dispatcher'), 2 | JPSConstants = require('../constants/jetpack-onboarding-constants'), 3 | SiteStore = require('stores/site-store'), 4 | FlashActions = require('./flash-actions.js'), 5 | SpinnerActions = require('./spinner-actions.js'), 6 | WPAjax = require('../utils/wp-ajax'); 7 | 8 | var SiteActions = { 9 | setTitle: function(title) { 10 | AppDispatcher.dispatch({ 11 | actionType: JPSConstants.SITE_SET_TITLE, 12 | title: title 13 | }); 14 | }, 15 | 16 | setType: function(type) { 17 | AppDispatcher.dispatch({ 18 | actionType: JPSConstants.SITE_SET_TYPE, 19 | type: type 20 | }); 21 | }, 22 | 23 | setDescription: function(description) { 24 | AppDispatcher.dispatch({ 25 | actionType: JPSConstants.SITE_SET_DESCRIPTION, 26 | description: description 27 | }); 28 | }, 29 | 30 | saveTitleAndDescription: function( title, description ) { 31 | 32 | WPAjax. 33 | post( JPS.site_actions.set_title, { title: title, description: description } ). 34 | fail( function ( msg ) { 35 | FlashActions.error("Error setting title: "+msg); 36 | }); 37 | 38 | jQuery('#wp-admin-bar-site-name .ab-item').html(title); 39 | 40 | // FlashActions.notice( "Set title to '"+title+"' and description to '"+description+"'" ); 41 | AppDispatcher.dispatch({ 42 | actionType: JPSConstants.SITE_SAVE_TITLE_AND_DESCRIPTION, 43 | title: title, 44 | description: description 45 | }); 46 | 47 | return jQuery.Deferred().resolve(); // XXX HACK 48 | }, 49 | 50 | saveBusinessAddress: function( businessAddress ) { 51 | WPAjax. 52 | post( JPS.site_actions.add_business_address, businessAddress ). 53 | fail( function ( msg ) { 54 | FlashActions.error("Error setting title: "+msg); 55 | }); 56 | 57 | const { business_address_1, business_address_2, business_city, business_name, business_state, business_zip } = businessAddress; 58 | 59 | JPS.bloginfo = Object.assign( {}, JPS.bloginfo, { business_address_1, business_address_2, business_city, business_name, business_state, business_zip } ); 60 | 61 | // FlashActions.notice( "Set title to '"+title+"' and description to '"+description+"'" ); 62 | AppDispatcher.dispatch({ 63 | actionType: JPSConstants.SITE_ADD_BUSINESS_ADDRESS, 64 | address: businessAddress 65 | }); 66 | 67 | return jQuery.Deferred().resolve(); // XXX HACK 68 | }, 69 | 70 | redirectToWooCommerceSetup: function() { 71 | AppDispatcher.dispatch({ 72 | actionType: JPSConstants.SITE_REDIRECT_TO_WOOCOMMERCE_SETUP 73 | }); 74 | }, 75 | 76 | installWooCommerce: function() { 77 | SpinnerActions.show( "Installing WooCommerce" ); 78 | AppDispatcher.dispatch({ 79 | actionType: JPSConstants.SITE_INSTALL_WOOCOMMERCE 80 | }); 81 | return WPAjax. 82 | post( JPS.site_actions.install_woocommerce ). 83 | done( function ( ) { 84 | AppDispatcher.dispatch({ 85 | actionType: JPSConstants.SITE_INSTALL_WOOCOMMERCE_SUCCESS 86 | }); 87 | }). 88 | fail( function ( msg ) { 89 | AppDispatcher.dispatch({ 90 | actionType: JPSConstants.SITE_INSTALL_WOOCOMMERCE_FAIL 91 | }); 92 | FlashActions.error( msg ); 93 | }). 94 | always( function() { 95 | SpinnerActions.hide(); 96 | }); 97 | }, 98 | 99 | setContactPageId: function(contactPageID) { 100 | AppDispatcher.dispatch({ 101 | actionType: JPSConstants.SITE_CONTACT_PAGE_ID, 102 | contactPageID: contactPageID 103 | }); 104 | }, 105 | 106 | _installTheme: function ( theme ) { 107 | if ( ! theme.installed ) { 108 | SpinnerActions.show("Installing '"+theme.name+"'"); 109 | return WPAjax. 110 | post( JPS.site_actions.install_theme, { themeId: theme.id } ). 111 | done( function ( ) { 112 | theme.installed = true; 113 | AppDispatcher.dispatch({ 114 | actionType: JPSConstants.SITE_INSTALL_THEME, 115 | theme: theme 116 | }); 117 | }). 118 | fail( function ( msg ) { 119 | FlashActions.error("Server error installing theme: "+msg); 120 | }). 121 | always( function() { 122 | SpinnerActions.hide(); 123 | }); 124 | } else { 125 | return jQuery.Deferred().resolve(); 126 | } 127 | }, 128 | 129 | _activateTheme: function ( theme ) { 130 | WPAjax. 131 | post( JPS.site_actions.set_theme, { themeId: theme.id } ). 132 | fail( function ( msg ) { 133 | FlashActions.error("Server error setting theme: "+msg); 134 | }); 135 | 136 | AppDispatcher.dispatch({ 137 | actionType: JPSConstants.SITE_SET_THEME, 138 | themeId: theme.id 139 | }); 140 | }, 141 | 142 | setActiveTheme: function( theme ) { 143 | 144 | this._installTheme(theme). 145 | done( function() { 146 | this._activateTheme(theme); 147 | }.bind(this)); 148 | 149 | return jQuery.Deferred().resolve(); // XXX HACK 150 | }, 151 | 152 | setLayout: function( layoutName ) { 153 | 154 | WPAjax. 155 | post( JPS.site_actions.set_layout, { layout: layoutName } ). 156 | done( function ( page_info ){ 157 | AppDispatcher.dispatch( { 158 | actionType: JPSConstants.SITE_CREATE_LAYOUT_PAGES, 159 | data: page_info 160 | } ); 161 | } ). 162 | fail( function (msg ){ 163 | FlashActions.error("Error setting layout: "+msg); 164 | } ); 165 | 166 | // FlashActions.notice("Set layout to "+layoutName); 167 | AppDispatcher.dispatch( { 168 | actionType: JPSConstants.SITE_SET_LAYOUT, 169 | layout: layoutName 170 | } ); 171 | 172 | return jQuery.Deferred().resolve(); // XXX HACK 173 | }, 174 | 175 | createContactUsPage: function( contactPage ) { 176 | 177 | return WPAjax. 178 | post( JPS.site_actions.build_contact_page, { buildContactPage: contactPage } ). 179 | done( function( page_info ) { 180 | AppDispatcher.dispatch({ 181 | actionType: JPSConstants.SITE_CREATE_CONTACT_US_PAGE, 182 | data: page_info 183 | }); 184 | }). 185 | fail( function( msg ) { 186 | FlashActions.error("Error creating contact us page: "+msg); 187 | }); 188 | }, 189 | 190 | skipContactPageBuild: function() { 191 | // FlashActions.notice( "Build the contact us page" ); 192 | AppDispatcher.dispatch({ 193 | actionType: JPSConstants.SITE_CREATE_CONTACT_US_PAGE 194 | }); 195 | 196 | return jQuery.Deferred().resolve(); // XXX HACK 197 | }, 198 | 199 | configureJetpack: function(return_to_step) { 200 | 201 | 202 | /**************** 203 | 204 | complete step 205 | 206 | *********************/ 207 | 208 | return WPAjax. 209 | post( JPS.site_actions.configure_jetpack, { return_to_step: return_to_step } ). 210 | done( function ( data ) { 211 | AppDispatcher.dispatch({ 212 | actionType: JPSConstants.SITE_JETPACK_CONFIGURED 213 | }); 214 | 215 | if ( data.next ) { 216 | window.location.replace(data.next); 217 | } 218 | }). 219 | fail( function ( msg ) { 220 | FlashActions.error("Error enabling Jetpack: "+msg); 221 | }); 222 | }, 223 | 224 | activateJetpackModule: function(module_slug) { 225 | 226 | WPAjax. 227 | post( JPS.site_actions.activate_jetpack_modules, { modules: [module_slug] }). 228 | fail( function ( msg ) { 229 | FlashActions.error("Error activating Jetpack module: "+msg); 230 | }); 231 | 232 | AppDispatcher.dispatch({ 233 | actionType: JPSConstants.SITE_JETPACK_MODULE_ENABLED, 234 | slug: module_slug 235 | }); 236 | 237 | return jQuery.Deferred().resolve(); // XXX HACK 238 | }, 239 | 240 | deactivateJetpackModule: function(module_slug) { 241 | 242 | WPAjax. 243 | post( JPS.site_actions.deactivate_jetpack_modules, { modules: [module_slug] }). 244 | fail( function ( msg ) { 245 | FlashActions.error("Error deactivating Jetpack module: "+msg); 246 | }); 247 | 248 | AppDispatcher.dispatch({ 249 | actionType: JPSConstants.SITE_JETPACK_MODULE_DISABLED, 250 | slug: module_slug 251 | }); 252 | 253 | return jQuery.Deferred().resolve(); // XXX HACK 254 | }, 255 | 256 | loadAllJetpackModules: function() { 257 | if ( SiteStore.getJetpackAdditionalModules().length === 0 ) { 258 | return WPAjax. 259 | post( JPS.site_actions.list_jetpack_modules ). 260 | done( function ( all_modules ) { 261 | AppDispatcher.dispatch({ 262 | actionType: JPSConstants.SITE_JETPACK_ADD_MODULES, 263 | modules: all_modules 264 | }); 265 | }). 266 | fail( function ( msg ) { 267 | FlashActions.error("Error fetching all Jetpack modules: "+msg); 268 | }); 269 | } else { 270 | return jQuery.Deferred().resolve(); // XXX HACK 271 | } 272 | }, 273 | 274 | enableJumpstart: function() { 275 | WPAjax. 276 | post( JPS.site_actions.activate_jetpack_modules, { modules: SiteStore.getJumpstartModuleSlugs() }). 277 | fail( function ( msg ) { 278 | FlashActions.error("Error activating Jetpack modules: "+msg); 279 | }); 280 | 281 | AppDispatcher.dispatch({ 282 | actionType: JPSConstants.SITE_JETPACK_JUMPSTART_ENABLED 283 | }); 284 | 285 | return jQuery.Deferred().resolve(); // XXX HACK 286 | } 287 | }; 288 | 289 | module.exports = SiteActions; -------------------------------------------------------------------------------- /client/actions/spinner-actions.js: -------------------------------------------------------------------------------- 1 | var AppDispatcher = require('../dispatcher/app-dispatcher'), 2 | JPSConstants = require('../constants/jetpack-onboarding-constants'); 3 | 4 | var SpinnerActions = { 5 | show: function(msg) { 6 | AppDispatcher.dispatch({ 7 | actionType: JPSConstants.SHOW_SPINNER, 8 | message: msg 9 | }); 10 | }, 11 | 12 | hide: function() { 13 | AppDispatcher.dispatch({ 14 | actionType: JPSConstants.HIDE_SPINNER, 15 | }); 16 | }, 17 | 18 | showAsync: function(msg) { 19 | AppDispatcher.dispatch({ 20 | actionType: JPSConstants.SHOW_ASYNC_SPINNER, 21 | message: msg 22 | }); 23 | }, 24 | 25 | hideAsync: function() { 26 | AppDispatcher.dispatch({ 27 | actionType: JPSConstants.HIDE_ASYNC_SPINNER 28 | }); 29 | } 30 | }; 31 | 32 | module.exports = SpinnerActions; -------------------------------------------------------------------------------- /client/components/dashicon.jsx: -------------------------------------------------------------------------------- 1 | // simple noticon wrapper 2 | 3 | var React = require('react'); 4 | 5 | var Dashicon = React.createClass({ 6 | 7 | propTypes: { 8 | name: React.PropTypes.string.isRequired 9 | }, 10 | 11 | render: function() { 12 | var { name, ...other } = this.props; 13 | 14 | return ( 15 | 16 | {this.props.children} 17 | 18 | ); 19 | } 20 | }); 21 | 22 | module.exports = Dashicon; -------------------------------------------------------------------------------- /client/components/flash.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Displays a flash message, if set. 3 | * JSON structure: 4 | * { severity: 'notice', message: 'My message' } 5 | * 6 | * Valid severities: 7 | * - error, notice 8 | */ 9 | 10 | var React = require('react'), 11 | FlashStore = require('stores/flash-store'); 12 | 13 | function getFlashState() { 14 | return FlashStore.getFlash(); 15 | } 16 | 17 | var Flash = React.createClass( { 18 | componentDidMount: function() { 19 | FlashStore.addChangeListener( this._onChange ); 20 | }, 21 | 22 | componentWillUnmount: function() { 23 | FlashStore.removeChangeListener( this._onChange ); 24 | }, 25 | 26 | _onChange: function() { 27 | this.setState( getFlashState() ); 28 | }, 29 | 30 | getInitialState: function() { 31 | return getFlashState(); 32 | }, 33 | 34 | render: function() { 35 | if ( this.state.message ) { 36 | return (
{ this.state.message }
); 37 | } else { 38 | return null; 39 | } 40 | } 41 | } ); 42 | 43 | module.exports = Flash; -------------------------------------------------------------------------------- /client/components/page/container.jsx: -------------------------------------------------------------------------------- 1 | var React = require( 'react' ); 2 | 3 | var WelcomeSection = React.createClass( { 4 | render: function() { 5 | var { ...other } = this.props; 6 | return ( 7 |
8 | { this.props.children } 9 |
10 | ); 11 | } 12 | } ); 13 | 14 | module.exports = WelcomeSection; 15 | -------------------------------------------------------------------------------- /client/components/page/index.jsx: -------------------------------------------------------------------------------- 1 | var React = require( 'react'), 2 | SetupProgressStore = require( 'stores/setup-progress-store'), 3 | SetupProgressActions = require( 'actions/setup-progress-actions'), 4 | SpinnerStore = require( 'stores/spinner-store' ), 5 | SpinnerActions = require( 'actions/spinner-actions'), 6 | DataStore = require( 'stores/data-store' ), 7 | Flash = require( '../flash' ), 8 | GetStarted = require( '../steps/get-started' ); 9 | 10 | function getSetupProgress() { 11 | return { 12 | newUser: SetupProgressStore.isNewUser(), 13 | showSpinner: SpinnerStore.showing(), 14 | spinnerMessage: SpinnerStore.getMessage(), 15 | currentStep: SetupProgressStore.getCurrentStep(), 16 | allSteps: SetupProgressStore.getAllSteps(), 17 | progressPercent: SetupProgressStore.getProgressPercent() 18 | }; 19 | } 20 | 21 | // TODO: visual "saving" for this.state.saving 22 | module.exports = React.createClass( { 23 | displayName: 'WelcomeWidget', 24 | 25 | componentDidMount: function() { 26 | SetupProgressStore.addChangeListener( this._onChange ); 27 | SpinnerStore.addChangeListener( this._onSpinnerChange ); 28 | DataStore.addChangeListener( this._onDataChange ); 29 | }, 30 | 31 | componentWillUnmount: function() { 32 | SetupProgressStore.removeChangeListener( this._onChange ); 33 | SpinnerStore.removeChangeListener( this._onSpinnerChange ); 34 | DataStore.removeChangeListener( this._onDataChange ); 35 | }, 36 | 37 | _onChange: function() { 38 | this.setState( getSetupProgress() ); 39 | }, 40 | 41 | _onSpinnerChange: function() { 42 | this.setState( { showSpinner: SpinnerStore.showing(), spinnerMessage: SpinnerStore.getMessage() } ); 43 | }, 44 | 45 | _onDataChange: function() { 46 | this.setState( { saving: DataStore.isSaving() } ); 47 | }, 48 | 49 | getInitialState: function() { 50 | return getSetupProgress(); 51 | }, 52 | 53 | handleReset: function( e ) { 54 | e.preventDefault(); 55 | SetupProgressActions.resetData(); 56 | }, 57 | 58 | handleShowSpinner: function ( e ) { 59 | e.preventDefault(); 60 | SpinnerActions.show( "Testing spinner" ); 61 | }, 62 | 63 | handleHideSpinner: function ( e ) { 64 | e.preventDefault(); 65 | SpinnerActions.hide(); 66 | }, 67 | 68 | render: function() { 69 | return ( 70 |
71 | { this._renderDebug() } 72 |
73 | { this._renderSpinner() } 74 |
75 | 76 | { this._renderSection() } 77 |
78 |
79 |
80 | ); 81 | }, 82 | 83 | _renderSection: function() { 84 | if ( this.state.newUser ) { 85 | return ( ); 86 | } else { 87 | return this._renderCurrentView(); 88 | } 89 | }, 90 | 91 | _renderDebug: function() { 92 | if ( JPS.debug ) { 93 | return ( 94 |
95 | Reset Wizard 96 | Show spinner 97 | Hide spinner 98 |
99 | ); 100 | } else { 101 | return null; 102 | } 103 | }, 104 | 105 | _renderSpinner: function() { 106 | if ( this.state.showSpinner ) { 107 | return ( 108 |
109 |
110 | 111 |   { this.state.spinnerMessage } 112 |
113 |
114 | ); 115 | 116 | } else { 117 | return null; 118 | } 119 | }, 120 | 121 | _renderCurrentView: function() { 122 | if ( this.state.currentStep ) { 123 | return ( ); 124 | } else { 125 | return (

Nothing

); 126 | } 127 | }, 128 | 129 | } ); 130 | -------------------------------------------------------------------------------- /client/components/page/section.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | var ContentBox = React.createClass({ 4 | render: function() { 5 | return ( 6 |
7 | { this.props.children } 8 |
9 | ); 10 | } 11 | }); 12 | 13 | module.exports = ContentBox; 14 | -------------------------------------------------------------------------------- /client/components/skip-button.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'), 2 | SetupProgressStore = require('stores/setup-progress-store'), 3 | SetupProgressActions = require('actions/setup-progress-actions'), 4 | Button = require('@automattic/dops-components/client/components/button'); 5 | 6 | function getSetupProgress() { 7 | return { 8 | completed: SetupProgressStore.getCurrentStep().completed 9 | }; 10 | } 11 | 12 | var SkipButton = React.createClass({ 13 | componentDidMount: function() { 14 | SetupProgressStore.addChangeListener(this._onChange); 15 | }, 16 | 17 | componentWillUnmount: function() { 18 | SetupProgressStore.removeChangeListener(this._onChange); 19 | }, 20 | 21 | _onChange: function() { 22 | this.setState(getSetupProgress()); 23 | }, 24 | 25 | getInitialState: function() { 26 | return getSetupProgress(); 27 | }, 28 | 29 | handleSkip: function (e) { 30 | e.preventDefault(); 31 | 32 | if ( this.props.handleSkip ) { 33 | return this.props.handleSkip(); 34 | } 35 | 36 | SetupProgressActions.skipStep(); 37 | }, 38 | 39 | render: function() { 40 | var completed = ( this.state.completed ); 41 | if ( completed ) { 42 | return null; 43 | } else { 44 | return ( 45 | 46 | ); 47 | } 48 | } 49 | }); 50 | 51 | module.exports = SkipButton; 52 | -------------------------------------------------------------------------------- /client/components/steps/business-address.jsx: -------------------------------------------------------------------------------- 1 | var React = require( 'react' ), 2 | SkipButton = require( '../skip-button' ), 3 | SiteStore = require( 'stores/site-store' ), 4 | WelcomeSection = require( '../page/container' ), 5 | SetupProgressActions = require( 'actions/setup-progress-actions' ), 6 | Button = require( '@automattic/dops-components/client/components/button' ); 7 | 8 | function getJetpackState() { 9 | return { 10 | site_title: SiteStore.getTitle(), 11 | jetpackConfigured: SiteStore.getJetpackConfigured(), 12 | jumpstartEnabled: SiteStore.getJetpackJumpstartEnabled(), 13 | modulesEnabled: SiteStore.getActiveModuleSlugs(), 14 | settingsUrl: SiteStore.getJetpackSettingsUrl() 15 | }; 16 | } 17 | 18 | module.exports = React.createClass( { 19 | 20 | componentDidMount: function() { 21 | SiteStore.addChangeListener( this._onChange ); 22 | }, 23 | 24 | componentWillUnmount: function() { 25 | SiteStore.removeChangeListener( this._onChange ); 26 | }, 27 | 28 | _onChange: function() { 29 | this.setState( getJetpackState() ); 30 | }, 31 | 32 | getInitialState: function() { 33 | var state = getJetpackState(); 34 | state.showMoreModules = false; 35 | state.jetpackConnecting = false; 36 | const { business_address_1, business_address_2, business_city, business_state, business_zip } = JPS.bloginfo; 37 | let business_name = JPS.bloginfo.business_name; 38 | if ( 'undefined' === typeof business_name ) { 39 | business_name = state.site_title; 40 | } 41 | state = Object.assign( {}, state, { business_address_1, business_address_2, business_city, business_name, business_state, business_zip } ); 42 | return state; 43 | }, 44 | 45 | handleChange: function( e ) { 46 | var newValue = {}; 47 | if ( 'checkbox' === e.currentTarget.type ) { 48 | newValue[ e.currentTarget.name ] = e.currentTarget.checked; 49 | } else { 50 | newValue[ e.currentTarget.name ] = e.currentTarget.value; 51 | } 52 | this.setState( newValue ); 53 | }, 54 | 55 | handleSubmit: function( e ) { 56 | e.preventDefault(); 57 | SetupProgressActions.submitBusinessAddress( this.state ); 58 | }, 59 | 60 | render: function() { 61 | return ( 62 | 63 |

Let's launch {this.state.site_title}

64 |

Add your business address (if you have one)

65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 |
73 | 74 | 75 |
76 |
77 | 78 |
79 | ); 80 | } 81 | } ); 82 | 83 | -------------------------------------------------------------------------------- /client/components/steps/contact.jsx: -------------------------------------------------------------------------------- 1 | var React = require( 'react' ), 2 | SiteStore = require( 'stores/site-store' ), 3 | Button = require( '@automattic/dops-components/client/components/button' ), 4 | WelcomeSection = require( '../page/container' ), 5 | SetupProgressActions = require( 'actions/setup-progress-actions' ), 6 | Paths = require('../../constants/jetpack-onboarding-paths'); 7 | 8 | function getSiteContactState() { 9 | return { 10 | site_title: SiteStore.getTitle(), 11 | contactPageURL: SiteStore.getContactPageURL(), 12 | contactPageScreenshot : `${ JPS.base_url }/img/jpo-contact.jpg` 13 | }; 14 | } 15 | 16 | var ContactPageStep = React.createClass( { 17 | 18 | componentDidMount: function() { 19 | SiteStore.addChangeListener( this._onChange ); 20 | }, 21 | 22 | componentWillUnmount: function() { 23 | SiteStore.removeChangeListener( this._onChange ); 24 | }, 25 | 26 | _onChange: function() { 27 | this.setState( getSiteContactState() ); 28 | }, 29 | 30 | getInitialState: function() { 31 | return getSiteContactState(); 32 | }, 33 | 34 | handleBuildContact: function( e ) { 35 | e.preventDefault(); 36 | SetupProgressActions.createContactPage(); 37 | }, 38 | 39 | handleSubmit: function( e ) { 40 | e.preventDefault(); 41 | SetupProgressActions.skipContactPageBuild(); 42 | }, 43 | 44 | handleContinue: function( e ) { 45 | e.preventDefault(); 46 | SetupProgressActions.completeStepNoRecord( Paths.CONTACT_PAGE_STEP_SLUG ); 47 | SetupProgressActions.selectNextStep(); 48 | }, 49 | 50 | render: function() { 51 | return( 52 | 53 |

Let's launch {this.state.site_title}

54 | 55 | { this.state.contactPageURL ? 56 | this._renderWithContactPage() : 57 | this._renderWithoutContactPage() 58 | } 59 |
60 | ); 61 | }, 62 | 63 | _renderWithContactPage: function() { 64 | return ( 65 |
66 |

View your starter Contact Us page.

67 |

68 | 69 |

70 |
71 | ); 72 | }, 73 | 74 | _renderWithoutContactPage: function() { 75 | return ( 76 |
77 |

Build a starter "Contact Us" page?

78 | 79 |
80 | 81 | 82 |
83 |
84 | ); 85 | } 86 | }); 87 | 88 | module.exports = ContactPageStep; 89 | -------------------------------------------------------------------------------- /client/components/steps/get-started.jsx: -------------------------------------------------------------------------------- 1 | var React = require( 'react' ), 2 | SetupProgressStore = require( 'stores/setup-progress-store' ), 3 | SetupProgressActions = require( 'actions/setup-progress-actions' ), 4 | Button = require( '@automattic/dops-components/client/components/button' ), 5 | Paths = require('../../constants/jetpack-onboarding-paths'); 6 | 7 | function getSetupState() { 8 | return {}; 9 | } 10 | 11 | var GetStarted = React.createClass({ 12 | componentDidMount: function() { 13 | SetupProgressStore.addChangeListener(this._onChange); 14 | }, 15 | 16 | componentWillUnmount: function() { 17 | SetupProgressStore.removeChangeListener(this._onChange); 18 | }, 19 | 20 | _onChange: function() { 21 | this.setState(getSetupState()); 22 | }, 23 | 24 | getInitialState: function() { 25 | return getSetupState(); 26 | }, 27 | 28 | handleGetStarted: function(sitePurpose, e) { 29 | e.preventDefault(); 30 | 31 | if ( 'personal' === sitePurpose ) { 32 | // We want to mark business only steps as complete, so they don't linger as pending steps 33 | // within the personal flow. 34 | SetupProgressActions.completeStepNoRecord( Paths.BUSINESS_ADDRESS_SLUG ); 35 | SetupProgressActions.completeStepNoRecord( Paths.WOOCOMMERCE_SLUG ); 36 | } 37 | 38 | SetupProgressActions.getStarted( sitePurpose ); 39 | }, 40 | 41 | handleNoThanks: function(e) { 42 | e.preventDefault(); 43 | SetupProgressActions.disableJPO(); 44 | }, 45 | 46 | render: function() { 47 | return ( 48 |
49 |
50 |

Welcome to WordPress

51 |
52 |

What kind of site can we help you set up?

53 |

54 | 55 | 56 |

57 |

58 | I don't need help 59 |

60 |
61 | 62 |
63 | ); 64 | } 65 | }); 66 | 67 | module.exports = GetStarted; 68 | -------------------------------------------------------------------------------- /client/components/steps/homepage.jsx: -------------------------------------------------------------------------------- 1 | var React = require( 'react' ), 2 | classNames = require( 'classnames' ), 3 | SiteStore = require( 'stores/site-store' ), 4 | Button = require( '@automattic/dops-components/client/components/button' ), 5 | WelcomeSection = require( '../page/container' ), 6 | SetupProgressActions = require( 'actions/setup-progress-actions' ); 7 | 8 | function getSiteLayoutState() { 9 | return { 10 | site_title: SiteStore.getTitle(), 11 | layout: SiteStore.getLayout(), 12 | siteScreenshot: `${ JPS.base_url }/img/jpo-layout-static.jpg`, 13 | blogScreenshot: `${ JPS.base_url }/img/jpo-layout-news.jpg`, 14 | }; 15 | } 16 | 17 | var HomepageStep = React.createClass( { 18 | 19 | componentDidMount: function() { 20 | SiteStore.addChangeListener( this._onChange ); 21 | }, 22 | 23 | componentWillUnmount: function() { 24 | SiteStore.removeChangeListener( this._onChange ); 25 | }, 26 | 27 | _onChange: function() { 28 | this.setState( getSiteLayoutState() ); 29 | }, 30 | 31 | getInitialState: function() { 32 | return getSiteLayoutState(); 33 | }, 34 | 35 | handleSetLayout: function( e ) { 36 | let layout = jQuery( e.currentTarget ).val(); 37 | this.setState( { layout: layout } ); 38 | SetupProgressActions.submitLayoutStep( layout ); 39 | }, 40 | 41 | skipStep: function( e ) { 42 | e.preventDefault(); 43 | let layout = 'blog'; 44 | this.setState( { layout: layout } ); 45 | SetupProgressActions.submitLayoutStep( layout ); 46 | }, 47 | 48 | render: function() { 49 | return ( 50 | 51 |

Let's launch { this.state.site_title }

52 |

What should visitors see on your homepage?

53 |
54 |
55 |
56 | 61 |
62 |
63 | 68 |
69 |
70 |
71 | 72 |
73 |
74 |
75 | ); 76 | } 77 | } ); 78 | 79 | module.exports = HomepageStep; 80 | -------------------------------------------------------------------------------- /client/components/steps/jetpack-jumpstart.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'), 2 | SkipButton = require('../skip-button'), 3 | SiteStore = require('stores/site-store'), 4 | SiteActions = require('actions/site-actions'), 5 | Paths = require('../../constants/jetpack-onboarding-paths'), 6 | ContentBox = require('../page/section'), 7 | WelcomeSection = require('../page/container'), 8 | SetupProgressActions = require('actions/setup-progress-actions'), 9 | SpinnerStore = require('stores/spinner-store'), 10 | Button = require('@automattic/dops-components/client/components/button'); 11 | 12 | function getJetpackState() { 13 | return { 14 | site_title: SiteStore.getTitle(), 15 | jetpackConfigured: SiteStore.getJetpackConfigured(), 16 | jumpstartEnabled: SiteStore.getJetpackJumpstartEnabled(), 17 | modulesEnabled: SiteStore.getActiveModuleSlugs(), 18 | settingsUrl: SiteStore.getJetpackSettingsUrl() 19 | }; 20 | } 21 | 22 | var JetpackJumpstart = React.createClass({ 23 | 24 | componentDidMount: function() { 25 | SiteStore.addChangeListener( this._onChange ); 26 | }, 27 | 28 | componentWillUnmount: function() { 29 | SiteStore.removeChangeListener( this._onChange ); 30 | }, 31 | 32 | _onChange: function() { 33 | this.setState( getJetpackState() ); 34 | }, 35 | 36 | getInitialState: function() { 37 | var state = getJetpackState(); 38 | state.showMoreModules = false; 39 | state.jetpackConnecting = false; 40 | return state; 41 | }, 42 | 43 | handleJetpackConnect: function( event ) { 44 | event.preventDefault(); 45 | this.setState( { jetpackConnecting: true } ); 46 | SiteActions 47 | .configureJetpack( Paths.JETPACK_MODULES_STEP_SLUG ) 48 | .always(function() { 49 | this.setState( { jetpackConnecting: false } ); 50 | }.bind( this ) ); 51 | }, 52 | 53 | handleNext: function( event ) { 54 | event.preventDefault(); 55 | SetupProgressActions.completeStepNoRecord( Paths.JETPACK_MODULES_STEP_SLUG ); 56 | SetupProgressActions.selectNextStep(); 57 | }, 58 | 59 | handleSkip: function() { 60 | SetupProgressActions.skipStep(); 61 | SetupProgressActions.selectNextStep(); 62 | }, 63 | 64 | render: function() { 65 | return ( 66 | 67 |

Let's launch your new website

68 |

69 | Connect your Jetpack profile to improve security, track stats, and grow traffic 70 |

71 | { 72 | this.state.jetpackConfigured 73 | ?
74 |

Congratulations! You've enabled Jetpack and unlocked dozens of powerful features.

75 |

Check out the settings page…

76 |
77 | 78 |
79 |
80 | :
81 | 87 | { ! this.state.jetpackConnecting && } 88 |
89 | } 90 |
91 |

Grow and Track Your Community

92 | 93 |

Jetpack provides Stats, insights and visitor information.

94 |

Use Jetpack tools like Publicize, Sharing, Subscribing and Related Posts to increase traffic, and onsite engagement.

95 |
96 |
97 |

Increase Security and Site Speed

98 | 99 |

Gain peace of mind with Protect, the tool that has blocked billions of login attacks on millions of sites.

100 |

Photon utilizes the state-of-the-art WordPress.com content delivery network to load your gorgeous images super fast optimized for any device, and it’s completely free.

101 |
102 |
103 | ); 104 | } 105 | }); 106 | 107 | module.exports = JetpackJumpstart; 108 | -------------------------------------------------------------------------------- /client/components/steps/layout.jsx: -------------------------------------------------------------------------------- 1 | var React = require( 'react' ), 2 | SiteStore = require( 'stores/site-store' ), 3 | Button = require( '@automattic/dops-components/client/components/button' ), 4 | WelcomeSection = require( '../page/container' ), 5 | SetupProgressActions = require( 'actions/setup-progress-actions' ); 6 | 7 | function getSiteLayoutState() { 8 | return { 9 | site_title: SiteStore.getTitle(), 10 | layout: SiteStore.getLayout() 11 | }; 12 | } 13 | 14 | var LayoutStep = React.createClass({ 15 | 16 | componentDidMount: function() { 17 | SiteStore.addChangeListener( this._onChange ); 18 | }, 19 | 20 | componentWillUnmount: function() { 21 | SiteStore.removeChangeListener( this._onChange ); 22 | }, 23 | 24 | _onChange: function() { 25 | this.setState( getSiteLayoutState() ); 26 | }, 27 | 28 | getInitialState: function() { 29 | return getSiteLayoutState(); 30 | }, 31 | 32 | handleIsBlog: function(){ 33 | SetupProgressActions.confirmHomepageStep(); 34 | }, 35 | 36 | handleNotBlog: function(){ 37 | SetupProgressActions.submitLayoutStep( 'website' ); 38 | }, 39 | 40 | render: function() { 41 | return ( 42 | 43 |

Let's launch { this.state.site_title }

44 |

Are you going to update your site with news or blog posts?

45 |

46 | 47 | 48 |

49 |
50 | ); 51 | } 52 | }); 53 | 54 | module.exports = LayoutStep; 55 | -------------------------------------------------------------------------------- /client/components/steps/review.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'), 2 | Button = require('@automattic/dops-components/client/components/button'), 3 | SiteStore = require('stores/site-store'), 4 | Paths = require('../../constants/jetpack-onboarding-paths'), 5 | Dashicon = require('../dashicon'), 6 | SetupProgressActions = require( 'actions/setup-progress-actions' ), 7 | WelcomeSection = require('../page/container'); 8 | 9 | function getSiteState() { 10 | return { 11 | site_title: SiteStore.getTitle(), 12 | contactUrl: SiteStore.getContactPageEditURL(), 13 | welcomeUrl: SiteStore.getWelcomePageEditURL(), 14 | newsUrl: SiteStore.getNewsPageEditURL(), 15 | isJPConnected: SiteStore.getJetpackConfigured(), 16 | layout: SiteStore.getLayout(), 17 | wooCommerceStatus: SiteStore.getWooCommerceStatus(), 18 | wooCommerceSetupUrl: SiteStore.getWooCommerceSetupUrl(), 19 | pluginsUrl: SiteStore.getPluginsUrl() 20 | }; 21 | } 22 | 23 | var AdvancedSettingsStep = React.createClass({ 24 | 25 | getInitialState: function() { 26 | return getSiteState(); 27 | }, 28 | 29 | componentDidMount: function() { 30 | SiteStore.addChangeListener(this._onChange); 31 | }, 32 | 33 | componentWillUnmount: function() { 34 | SiteStore.removeChangeListener(this._onChange); 35 | }, 36 | 37 | _onChange: function() { 38 | this.setState(getSiteState()); 39 | }, 40 | 41 | handleSkipTo: function( slug, event ) { 42 | event.preventDefault(); 43 | SetupProgressActions.setCurrentStep( slug ); 44 | }, 45 | 46 | handleDismiss: function( event ) { 47 | event.preventDefault(); 48 | SetupProgressActions.closeJPO(); 49 | }, 50 | 51 | renderWooCommerceStatus: function() { 52 | const { is_shop, type } = JPS.bloginfo; 53 | if ( type !== 'business' || ! JPS.step_enabled[Paths.WOOCOMMERCE_SLUG] ) { 54 | return null; 55 | } 56 | 57 | if ( this.state.wooCommerceStatus ) { 58 | return ( 59 |
  • 60 | WooCommerce Installed! Set up shop 61 |
  • 62 | ); 63 | } else if ( ! is_shop ) { 64 | return ( 65 |
  • 66 | WooCommerce not installed. Install WooCommerce? 67 |
  • 68 | ) 69 | } else { 70 | return ( 71 |
  • 72 | Error installing WooCommerce Try manual installation 73 |
  • 74 | ) 75 | } 76 | 77 | }, 78 | 79 | render: function() { 80 | let contactProps = {}; 81 | if ( this.state.contactUrl ) { 82 | contactProps.href = this.state.contactUrl; 83 | } else { 84 | contactProps.href = '#'; 85 | contactProps.onClick = this.handleSkipTo.bind(this, Paths.CONTACT_PAGE_STEP_SLUG ); 86 | } 87 | return ( 88 | 89 |
    90 | Dismiss
    91 | 92 |

    Let's launch {this.state.site_title}

    93 |

    Great Work!

    94 | 95 |
    96 |
    97 |
      98 | { JPS.step_enabled[Paths.SITE_TITLE_STEP_SLUG] && 99 |
    • Title and description (edit)
    • 100 | } 101 | { JPS.step_enabled[Paths.IS_BLOG_STEP_SLUG] && 102 |
    • 103 | Homepage layout (edit) 104 | { ( JPS.step_enabled[Paths.IS_BLOG_STEP_SLUG] && this.state.layout !== 'blog' ) ? 105 | : 111 | null 112 | } 113 |
    • 114 | } 115 | { JPS.step_enabled[Paths.CONTACT_PAGE_STEP_SLUG] && 116 |
    • 117 | Contact Us page (edit) 118 | { ! this.state.isJPConnected ? Requires a Jetpack Connection : null } 119 |
    • 120 | } 121 | { JPS.step_enabled[Paths.JETPACK_MODULES_STEP_SLUG] && 122 |
    • 123 | 124 | { this.state.isJPConnected ? 125 | Jetpack: : 126 | Connect Jetpack: 127 | } 128 | increase visitors and improve security 129 |
    • 130 | } 131 | { ( JPS.step_enabled[Paths.BUSINESS_ADDRESS_SLUG] && JPS.bloginfo.type === 'business' ) ? 132 |
    • 133 | { JPS.steps.business_address 134 | ? 135 | : 136 | } Business Address page (edit) 137 | { ! this.state.isJPConnected ? Requires a Jetpack Connection : null } 138 |
    • : 139 | null 140 | } 141 | { this.renderWooCommerceStatus() } 142 |
    143 |
    144 | 145 | { JPS.steps.advanced_settings.show_cta ? 146 |
    147 | 148 |

    149 |
    : null 150 | } 151 |
    152 |
    153 | ); 154 | } 155 | }); 156 | 157 | module.exports = AdvancedSettingsStep; 158 | -------------------------------------------------------------------------------- /client/components/steps/site-title.jsx: -------------------------------------------------------------------------------- 1 | var React = require( 'react' ), 2 | SiteActions = require( 'actions/site-actions' ), 3 | SiteStore = require( 'stores/site-store' ), 4 | WelcomeSection = require( '../page/container' ), 5 | Button = require( '@automattic/dops-components/client/components/button' ), 6 | SetupProgressActions = require( 'actions/setup-progress-actions' ); 7 | 8 | function getSiteTitleState() { 9 | return { 10 | title: SiteStore.getTitle(), 11 | description: SiteStore.getDescription() 12 | }; 13 | } 14 | 15 | var SiteTitleStep = React.createClass( { 16 | 17 | componentDidMount: function() { 18 | SiteStore.addChangeListener( this._onChange ); 19 | }, 20 | 21 | componentWillUnmount: function() { 22 | SiteStore.removeChangeListener( this._onChange ); 23 | }, 24 | 25 | _onChange: function() { 26 | this.setState( getSiteTitleState() ); 27 | }, 28 | 29 | getInitialState: function() { 30 | return getSiteTitleState(); 31 | }, 32 | 33 | handleChangeTitle: function(e) { 34 | this.setState( { title: e.currentTarget.value } ); 35 | }, 36 | 37 | handleChangeDescription: function(e) { 38 | this.setState( { description: e.currentTarget.value } ); 39 | }, 40 | 41 | handleSubmit: function(e) { 42 | e.preventDefault(); 43 | SetupProgressActions.submitTitleStep( this.state.title, this.state.description ); 44 | }, 45 | 46 | render: function() { 47 | return ( 48 | 49 |

    Let's launch your new website

    50 |

    Name and describe your website

    51 |
    52 | 53 | 54 | 55 | 56 |
    57 | 58 |
    59 |
    60 |
    61 | ); 62 | } 63 | }); 64 | 65 | module.exports = SiteTitleStep; 66 | -------------------------------------------------------------------------------- /client/components/steps/woocommerce.jsx: -------------------------------------------------------------------------------- 1 | var React = require( 'react' ), 2 | SkipButton = require( '../skip-button' ), 3 | SiteStore = require( 'stores/site-store' ), 4 | SetupProgressActions = require( 'actions/setup-progress-actions' ), 5 | WelcomeSection = require( '../page/container' ), 6 | SiteActions = require( 'actions/site-actions' ), 7 | Paths = require( 'constants/jetpack-onboarding-paths' ), 8 | Button = require( '@automattic/dops-components/client/components/button' ); 9 | 10 | function getJetpackState() { 11 | const { is_shop, redirect_to_woocommerce_setup } = JPS.bloginfo; 12 | return { 13 | site_title: SiteStore.getTitle(), 14 | wooCommerceStatus: SiteStore.getWooCommerceStatus(), 15 | wooCommerceSetupUrl: SiteStore.getWooCommerceSetupUrl(), 16 | is_shop, 17 | redirect_to_woocommerce_setup 18 | }; 19 | } 20 | 21 | module.exports = React.createClass( { 22 | 23 | componentDidMount: function() { 24 | SiteStore.addChangeListener( this._onChange ); 25 | JPS.shownWoocommerceStep = true; 26 | }, 27 | 28 | componentWillUnmount: function() { 29 | SiteStore.removeChangeListener( this._onChange ); 30 | }, 31 | 32 | _onChange: function() { 33 | this.setState( getJetpackState() ); 34 | }, 35 | 36 | getInitialState: function() { 37 | return getJetpackState(); 38 | }, 39 | 40 | goToWooSetup: function() { 41 | jQuery( window ).off( 'beforeunload' ); 42 | SiteActions.redirectToWooCommerceSetup(); 43 | SetupProgressActions.completeStep( Paths.WOOCOMMERCE_SLUG ); 44 | window.location = this.state.wooCommerceSetupUrl; 45 | }, 46 | 47 | goToJpoReview: function() { 48 | SetupProgressActions.setCurrentStep( Paths.REVIEW_STEP_SLUG ); 49 | }, 50 | 51 | handleSubmit: function( event ) { 52 | event.preventDefault(); 53 | SiteActions.installWooCommerce(); 54 | }, 55 | 56 | renderInstall: function() { 57 | return ( 58 |
    59 |

    Are you looking to sell online?

    60 |
    61 | 62 |
    63 | 64 | 65 |
    66 |
    67 |
    68 | ); 69 | }, 70 | 71 | renderAlreadyInstalled: function() { 72 | return ( 73 |
    74 |

    WooCommerce is ready to go

    75 |
    76 | 77 | 78 |
    79 |
    80 | ); 81 | }, 82 | 83 | render: function() { 84 | return ( 85 | 86 |

    Let's launch {this.state.site_title}

    87 | { this.state.wooCommerceStatus ? 88 | this.renderAlreadyInstalled() : 89 | this.renderInstall() 90 | } 91 |
    92 | ); 93 | } 94 | } ); 95 | -------------------------------------------------------------------------------- /client/constants/jetpack-onboarding-constants.js: -------------------------------------------------------------------------------- 1 | var keyMirror = require('keymirror'); 2 | 3 | module.exports = keyMirror({ 4 | STEP_COMPLETE: null, 5 | STEP_GET_STARTED: null, 6 | STEP_SELECT: null, 7 | STEP_NEXT: null, 8 | STEP_SKIP: null, 9 | SITE_SET_TITLE: null, 10 | SITE_SET_TYPE: null, 11 | SITE_SET_DESCRIPTION: null, 12 | SITE_ADD_BUSINESS_ADDRESS: null, 13 | SITE_INSTALL_WOOCOMMERCE: null, 14 | SITE_INSTALL_WOOCOMMERCE_SUCCESS: null, 15 | SITE_INSTALL_WOOCOMMERCE_FAIL: null, 16 | SITE_REDIRECT_TO_WOOCOMMERCE_SETUP: null, 17 | SITE_SAVE_TITLE_AND_DESCRIPTION: null, 18 | SITE_CONTACT_PAGE_ID: null, 19 | SITE_SET_THEME: null, 20 | SITE_INSTALL_THEME: null, 21 | SITE_JETPACK_CONFIGURED: null, 22 | SITE_JETPACK_MODULE_ENABLED: null, 23 | SITE_JETPACK_MODULE_DISABLED: null, 24 | SITE_JETPACK_JUMPSTART_ENABLED: null, 25 | SITE_JETPACK_ADD_MODULES: null, 26 | SITE_SET_LAYOUT: null, 27 | 28 | SITE_CREATE_CONTACT_US_PAGE: null, 29 | SITE_CREATE_LAYOUT_PAGES: null, 30 | 31 | SAVE_STARTED: null, 32 | SAVE_FINISHED: null, 33 | 34 | SET_FLASH: null, 35 | UNSET_FLASH: null, 36 | FLASH_SEVERITY_NOTICE: null, 37 | FLASH_SEVERITY_ERROR: null, 38 | 39 | RESET_DATA: null, 40 | 41 | SHOW_SPINNER: null, 42 | HIDE_SPINNER: null 43 | }); 44 | -------------------------------------------------------------------------------- /client/constants/jetpack-onboarding-paths.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // steps 3 | SITE_TITLE_STEP_SLUG: 'title', 4 | IS_BLOG_STEP_SLUG: 'is-blog', 5 | HOMEPAGE_STEP_SLUG: 'homepage', 6 | TRAFFIC_STEP_SLUG: 'traffic', 7 | STATS_MONITORING_STEP_SLUG: 'stats-monitoring', 8 | DESIGN_STEP_SLUG: 'design', 9 | ADVANCED_STEP_SLUG: 'advanced', 10 | REVIEW_STEP_SLUG: 'review', 11 | JETPACK_MODULES_STEP_SLUG: 'jetpack', 12 | CONTACT_PAGE_STEP_SLUG: 'contact-page', 13 | BUSINESS_ADDRESS_SLUG: 'business-address', 14 | WOOCOMMERCE_SLUG: 'woocommerce' 15 | }; 16 | -------------------------------------------------------------------------------- /client/dispatcher/app-dispatcher.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * AppDispatcher 10 | * 11 | * A singleton that operates as the central hub for application updates. 12 | */ 13 | 14 | var Dispatcher = require('flux').Dispatcher; 15 | 16 | module.exports = new Dispatcher(); -------------------------------------------------------------------------------- /client/ie-shims.js: -------------------------------------------------------------------------------- 1 | require( 'es5-shim' ); 2 | require( 'es5-shim/es5-sham' ); -------------------------------------------------------------------------------- /client/jetpack-onboarding.js: -------------------------------------------------------------------------------- 1 | var WelcomePanel = require( './welcome-panel' ); 2 | 3 | WelcomePanel(); -------------------------------------------------------------------------------- /client/stores/data-store.js: -------------------------------------------------------------------------------- 1 | var AppDispatcher = require('../dispatcher/app-dispatcher'), 2 | EventEmitter = require('events').EventEmitter, 3 | JPSConstants = require('../constants/jetpack-onboarding-constants'); 4 | 5 | /* 6 | * This is a refcounted save monitor which warns if you try to leave the page while the data is still saving 7 | */ 8 | 9 | var _currentSaves = 0, jpoTimeout, CHANGE_EVENT = 'change'; 10 | 11 | function incrementSaveCounter() { 12 | _currentSaves = _currentSaves + 1; 13 | } 14 | 15 | function decrementSaveCounter() { 16 | _currentSaves = _currentSaves - 1; 17 | } 18 | 19 | var DataStore = _.extend({}, EventEmitter.prototype, { 20 | isSaving: function() { 21 | return _currentSaves > 0; 22 | }, 23 | 24 | addChangeListener: function(callback) { 25 | this.on(CHANGE_EVENT, callback); 26 | }, 27 | 28 | removeChangeListener: function(callback) { 29 | this.removeListener(CHANGE_EVENT, callback); 30 | }, 31 | 32 | emitChange: function() { 33 | this.emit(CHANGE_EVENT); 34 | }, 35 | }); 36 | 37 | jQuery(window).on('beforeunload', function() { 38 | if(DataStore.isSaving()) { 39 | jpoTimeout = setTimeout(function() { 40 | // alert('You stayed'); 41 | // noop 42 | }, 1000); 43 | return "Your site changes are still saving."; 44 | } 45 | }); 46 | 47 | jQuery(window).on('unload', function() { 48 | clearTimeout(jpoTimeout); 49 | }); 50 | 51 | AppDispatcher.register(function(action) { 52 | 53 | switch(action.actionType) { 54 | case JPSConstants.SAVE_STARTED: 55 | incrementSaveCounter(); 56 | DataStore.emitChange(); 57 | break; 58 | 59 | case JPSConstants.SAVE_FINISHED: 60 | decrementSaveCounter(); 61 | DataStore.emitChange(); 62 | break; 63 | 64 | default: 65 | // no op 66 | } 67 | }); 68 | 69 | module.exports = DataStore; -------------------------------------------------------------------------------- /client/stores/flash-store.js: -------------------------------------------------------------------------------- 1 | var AppDispatcher = require('../dispatcher/app-dispatcher'), 2 | EventEmitter = require('events').EventEmitter, 3 | JPSConstants = require('../constants/jetpack-onboarding-constants'); 4 | 5 | var CHANGE_EVENT = 'change'; 6 | var message, severity; 7 | 8 | function setFlash(newMessage, newSeverity) { 9 | message = newMessage; 10 | severity = newSeverity; 11 | } 12 | 13 | var FlashStore = _.extend({}, EventEmitter.prototype, { 14 | getFlash: function() { 15 | var severityString; 16 | 17 | switch(severity) { 18 | case JPSConstants.FLASH_SEVERITY_ERROR: 19 | severityString = 'error'; 20 | break; 21 | case JPSConstants.FLASH_SEVERITY_NOTICE: 22 | severityString = 'notice'; 23 | break; 24 | default: 25 | //noop 26 | } 27 | return {message: message, severity: severityString}; 28 | 29 | }, 30 | 31 | addChangeListener: function(callback) { 32 | this.on(CHANGE_EVENT, callback); 33 | }, 34 | 35 | removeChangeListener: function(callback) { 36 | this.removeListener(CHANGE_EVENT, callback); 37 | }, 38 | 39 | emitChange: function() { 40 | this.emit(CHANGE_EVENT); 41 | }, 42 | }); 43 | 44 | AppDispatcher.register(function(action) { 45 | 46 | switch(action.actionType) { 47 | case JPSConstants.SET_FLASH: 48 | setFlash(action.message, action.severity); 49 | FlashStore.emitChange(); 50 | break; 51 | 52 | case JPSConstants.UNSET_FLASH: 53 | setFlash(null, null); 54 | FlashStore.emitChange(); 55 | break; 56 | 57 | default: 58 | // no op 59 | } 60 | }); 61 | 62 | module.exports = FlashStore; -------------------------------------------------------------------------------- /client/stores/setup-progress-store.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Store which manages and persists setup wizard progress 3 | */ 4 | 5 | var AppDispatcher = require('../dispatcher/app-dispatcher'), 6 | EventEmitter = require('events').EventEmitter, 7 | JPSConstants = require('../constants/jetpack-onboarding-constants'); 8 | 9 | var urlTools = require( 'url' ); 10 | 11 | var CHANGE_EVENT = 'change'; 12 | 13 | var _steps, _started = JPS.started; 14 | 15 | function setSteps(steps) { 16 | 17 | // set the completion status of each step to the saved values 18 | steps.forEach( function(step) { 19 | // default values for skipped, completed and static 20 | if ( typeof( step.completed ) === 'undefined' ) { 21 | step.completed = (JPS.step_status[step.slug] && JPS.step_status[step.slug].completed) || false; 22 | } 23 | 24 | if ( typeof( step.skipped ) === 'undefined' ) { 25 | step.skipped = (JPS.step_status[step.slug] && JPS.step_status[step.slug].skipped) || false; 26 | } 27 | 28 | if ( typeof( step.static ) === 'undefined' ) { 29 | step.static = false; 30 | } 31 | 32 | // set to 'true' if you want the wizard to move to this step even if it's been completed 33 | // by default completed steps are skipped 34 | if ( typeof( step.neverSkip ) === 'undefined' ) { 35 | step.neverSkip = false; 36 | } 37 | 38 | // default value for includeInProgress 39 | if ( typeof( step.includeInProgress ) === 'undefined') { 40 | step.includeInProgress = true; 41 | } 42 | 43 | if ( typeof( step.enabled ) === 'undefined' ) { 44 | step.enabled = (JPS.step_enabled[step.slug]) || false; 45 | } 46 | }); 47 | 48 | _steps = steps; 49 | 50 | // set location to first pending step, if not set 51 | ensureValidStepSlug(); 52 | } 53 | 54 | function setStarted() { 55 | _started = true; 56 | selectNextPendingStep(); 57 | } 58 | 59 | function complete(stepSlug) { 60 | var step = getStepFromSlug(stepSlug); 61 | step.completed = true; 62 | step.skipped = false; 63 | } 64 | 65 | function skip() { 66 | var stepSlug = currentStepSlug(); 67 | var step = getStepFromSlug(stepSlug); 68 | step.skipped = true; 69 | selectNextPendingStep(); 70 | } 71 | 72 | function getStepFromSlug( stepSlug ) { 73 | var currentStep = null; 74 | _.each( _steps, function( step ) { 75 | if( step.slug === stepSlug ) { 76 | currentStep = step; 77 | } 78 | }); 79 | return currentStep; 80 | } 81 | 82 | function getUrlWithStepHash( hash ) { 83 | var url = urlTools.parse( window.location.href ); 84 | return url.path + '#' + hash; 85 | } 86 | 87 | function ensureValidStepSlug() { 88 | var stepSlug = currentStepSlug(); 89 | if ( ! ( stepSlug && getStepFromSlug( stepSlug ) ) ) { 90 | 91 | var pendingStep = getNextPendingStep(); 92 | if ( pendingStep !== null ) { 93 | var hash = 'welcome/steps/'+pendingStep.slug; 94 | window.history.pushState(null, document.title, getUrlWithStepHash( hash ) ); 95 | } 96 | } 97 | } 98 | 99 | function selectNextPendingStep() { 100 | var pendingStep = getNextPendingStep(); 101 | if ( pendingStep !== null ) { 102 | select(pendingStep.slug); // also sets the window location hash 103 | } 104 | } 105 | 106 | function getNextPendingStep() { 107 | // if the _next_ step is neverSkip, we proceed to it 108 | var stepIndex = currentStepIndex(); 109 | if ( stepIndex !== false ) { 110 | if ( _steps[stepIndex+1] && _steps[stepIndex+1].enabled && _steps[stepIndex+1].neverSkip === true ) { 111 | return _steps[stepIndex+1]; 112 | } 113 | } 114 | 115 | // otherwise find the next uncompleted, unskipped step 116 | var nextPendingStep = _.findWhere( _steps, { enabled: true, completed: false, skipped: false } ); 117 | return nextPendingStep; 118 | } 119 | 120 | function getPendingStepAfter( fromStep ) { 121 | 122 | } 123 | 124 | function currentStepSlug() { 125 | if ( window.location.hash.indexOf('#welcome/steps') === 0 ) { 126 | var parts = window.location.hash.split('/'); 127 | var stepSlug = parts[parts.length-1]; 128 | return stepSlug; 129 | } else { 130 | return null; 131 | } 132 | } 133 | 134 | function currentStepIndex() { 135 | var slug = currentStepSlug(); 136 | return getStepIndex(slug); 137 | } 138 | 139 | function getStepIndex(slug) { 140 | for ( var i=0; i<_steps.length; i++ ) { 141 | if ( _steps[i].slug === slug ) { 142 | return i; 143 | } 144 | } 145 | return false; 146 | } 147 | 148 | function select(stepSlug) { 149 | var hash = 'welcome/steps/'+stepSlug; 150 | window.history.pushState(null, document.title, getUrlWithStepHash( hash ) ); 151 | } 152 | 153 | //reset everything back to defaults 154 | function reset() { 155 | JPS.step_status = {}; 156 | _.where( _steps, { static: false} ).forEach( function ( step ) { 157 | step.completed = false; 158 | step.skipped = false; 159 | } ); 160 | _started = false; 161 | } 162 | 163 | var SetupProgressStore = _.extend({}, EventEmitter.prototype, { 164 | 165 | init: function(steps) { 166 | setSteps(steps); 167 | }, 168 | 169 | getAllSteps: function() { 170 | return _steps; 171 | }, 172 | 173 | isNewUser: function() { 174 | return !_started; 175 | }, 176 | 177 | emitChange: function() { 178 | this.emit( CHANGE_EVENT ); 179 | }, 180 | 181 | getCurrentStep: function() { 182 | return getStepFromSlug( currentStepSlug() ); 183 | }, 184 | 185 | getNextPendingStep: function() { 186 | return getNextPendingStep(); // delegate 187 | }, 188 | 189 | getStepFromSlug: function(slug) { 190 | return getStepFromSlug( slug ); // delegate 191 | }, 192 | 193 | getProgressPercent: function() { 194 | var numSteps = _.where( _steps, { includeInProgress: true } ).length; 195 | var completedSteps = _.where( _steps, { includeInProgress: true, completed: true } ).length; 196 | var percentComplete = (completedSteps / numSteps) * 90 + 10; 197 | var output = Math.round(percentComplete / 10) * 10; 198 | return output; 199 | }, 200 | 201 | addChangeListener: function(callback) { 202 | this.on( CHANGE_EVENT, callback ); 203 | }, 204 | 205 | removeChangeListener: function(callback) { 206 | this.removeListener( CHANGE_EVENT, callback ); 207 | } 208 | }); 209 | 210 | // force a navigation refresh when the URL changes 211 | window.addEventListener("popstate", function(){ 212 | SetupProgressStore.emitChange(); 213 | }); 214 | 215 | // Register callback to handle all updates 216 | AppDispatcher.register(function(action) { 217 | 218 | switch(action.actionType) { 219 | case JPSConstants.STEP_GET_STARTED: 220 | setStarted(); 221 | SetupProgressStore.emitChange(); 222 | break; 223 | 224 | case JPSConstants.STEP_SELECT: 225 | select(action.slug); 226 | SetupProgressStore.emitChange(); 227 | break; 228 | 229 | case JPSConstants.STEP_NEXT: 230 | selectNextPendingStep(); 231 | SetupProgressStore.emitChange(); 232 | break; 233 | 234 | case JPSConstants.STEP_COMPLETE: 235 | complete(action.slug); 236 | SetupProgressStore.emitChange(); 237 | break; 238 | 239 | case JPSConstants.RESET_DATA: 240 | reset(); 241 | SetupProgressStore.emitChange(); 242 | break; 243 | 244 | case JPSConstants.STEP_SKIP: 245 | skip(); 246 | SetupProgressStore.emitChange(); 247 | break; 248 | 249 | default: 250 | // no op 251 | } 252 | }); 253 | 254 | module.exports = SetupProgressStore; -------------------------------------------------------------------------------- /client/stores/site-store.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Store which manages and persists site information 3 | */ 4 | 5 | var AppDispatcher = require('../dispatcher/app-dispatcher'), 6 | EventEmitter = require('events').EventEmitter, 7 | JPSConstants = require('../constants/jetpack-onboarding-constants'), 8 | WPAjax = require('../utils/wp-ajax'); 9 | 10 | var CHANGE_EVENT = 'change'; 11 | 12 | var layout = JPS.steps.layout.current; 13 | 14 | function setType(newType) { 15 | JPS.bloginfo.type = newType; 16 | } 17 | 18 | function setTitle(newTitle) { 19 | JPS.bloginfo.name = newTitle; 20 | } 21 | 22 | function setDescription(newDescription) { 23 | JPS.bloginfo.description = newDescription; 24 | } 25 | 26 | function setActiveTheme(activeThemeId) { 27 | JPS.themes.forEach( function( theme ) { 28 | if ( theme.id === activeThemeId ) { 29 | theme.active = true; 30 | } else { 31 | theme.active = false; 32 | } 33 | } ); 34 | } 35 | 36 | function installedTheme(theme) { 37 | JPS.themes.unshift(theme); 38 | JPS.themes = JPS.themes.slice(0, 3); 39 | } 40 | 41 | function setJetpackModuleActivated(slug) { 42 | if ( _.indexOf( JPS.jetpack.active_modules, slug ) === -1 ) { 43 | JPS.jetpack.active_modules.push(slug); 44 | } 45 | } 46 | 47 | function setJetpackModuleDectivated(slug) { 48 | var index = _.indexOf( JPS.jetpack.active_modules, slug ); 49 | if ( index >= 0) { 50 | JPS.jetpack.active_modules.splice(index, 1); 51 | } 52 | } 53 | 54 | function setJetpackAdditionalModules(modules) { 55 | JPS.jetpack.additional_modules = _.filter(modules, function(module) { 56 | return _.indexOf(JPS.jetpack.jumpstart_modules.map(function(mod){return mod.slug;}), module.slug) === -1; 57 | }); 58 | } 59 | 60 | function setLayout(layoutName) { 61 | layout = layoutName; // XXX TODO: get this value dynamically from the server! 62 | } 63 | 64 | function setJetpackConfigured() { 65 | JPS.jetpack.configured = true; 66 | } 67 | 68 | function setJetpackJumpstartActivated() { 69 | JPS.jetpack.jumpstart_modules.forEach( function( module ) { 70 | setJetpackModuleActivated( module.slug ); 71 | }); 72 | } 73 | 74 | function setContactUsPage( pageInfo ) { 75 | JPS.steps.contact_page = pageInfo; 76 | } 77 | 78 | function setLayoutPages( pageInfo ) { 79 | JPS.steps.layout.welcomeEditUrl = pageInfo.welcome; 80 | JPS.steps.layout.postsEditUrl = pageInfo.posts; 81 | } 82 | 83 | function setShopStatus() { 84 | JPS.bloginfo = Object.assign( {}, JPS.bloginfo, { is_shop: true } ); 85 | } 86 | 87 | function setWooCommerceStatus() { 88 | JPS.woocommerce_status = true; 89 | JPS.bloginfo = Object.assign( {}, JPS.bloginfo, { redirect_to_woocommerce_setup: true } ); 90 | } 91 | 92 | function setWooCommerceRedirectStatus() { 93 | JPS.bloginfo = Object.assign( {}, JPS.bloginfo, { redirect_to_woocommerce_setup: false } ); 94 | } 95 | 96 | var SiteStore = _.extend({}, EventEmitter.prototype, { 97 | 98 | getTitle: function() { 99 | return JPS.bloginfo.name; 100 | }, 101 | 102 | getType: function() { 103 | return JPS.bloginfo.type; 104 | }, 105 | 106 | getDescription: function() { 107 | return JPS.bloginfo.description; 108 | }, 109 | 110 | getContactPageURL: function() { 111 | return JPS.steps.contact_page && JPS.steps.contact_page.url; 112 | }, 113 | 114 | getContactPageEditURL: function() { 115 | if ( JPS.steps.contact_page && JPS.steps.contact_page.editUrl ) { 116 | return JPS.steps.contact_page.editUrl.replace('&','&'); 117 | } 118 | }, 119 | 120 | getWelcomePageEditURL: function() { 121 | if ( JPS.steps.layout && JPS.steps.layout.welcomeEditUrl ) { 122 | return JPS.steps.layout.welcomeEditUrl.replace('&','&'); 123 | } 124 | }, 125 | 126 | getNewsPageEditURL: function() { 127 | if ( JPS.steps.layout && JPS.steps.layout.postsEditUrl ) { 128 | return JPS.steps.layout.postsEditUrl.replace('&','&'); 129 | } 130 | }, 131 | 132 | getThemes: function() { 133 | return JPS.themes; 134 | }, 135 | 136 | getActiveThemeId: function() { 137 | for(var i=0; i < JPS.themes.length; i++) { 138 | var theme = JPS.themes[i]; 139 | if ( theme.active ) { 140 | return theme.id; 141 | } 142 | } 143 | return null; 144 | }, 145 | 146 | getWooCommerceStatus: function() { 147 | return JPS.woocommerce_status; 148 | }, 149 | 150 | getWooCommerceSetupUrl: function() { 151 | return JPS.steps.advanced_settings.woocommerce_setup_url; 152 | }, 153 | 154 | getJetpackConfigured: function() { 155 | return JPS.jetpack.configured; 156 | }, 157 | 158 | getActiveModuleSlugs: function() { 159 | return JPS.jetpack.active_modules; 160 | }, 161 | 162 | isJetpackModuleEnabled: function(slug) { 163 | return ( _.indexOf( JPS.jetpack.active_modules, slug ) >= 0 ); 164 | }, 165 | 166 | getJetpackAdditionalModules: function() { 167 | return JPS.jetpack.additional_modules; 168 | }, 169 | 170 | getJumpstartModuleSlugs: function() { 171 | return JPS.jetpack.jumpstart_modules.map(function(module) { return module.slug; }); 172 | }, 173 | 174 | getJumpstartModules: function() { 175 | return JPS.jetpack.jumpstart_modules; 176 | }, 177 | 178 | getJetpackSettingsUrl: function() { 179 | return JPS.steps.advanced_settings && JPS.steps.advanced_settings.jetpack_modules_url; 180 | }, 181 | 182 | getPluginsUrl: function() { 183 | return JPS.steps.advanced_settings.plugins_url; 184 | }, 185 | 186 | getPopularThemes: function() { 187 | return WPAjax.post(JPS.site_actions.get_popular_themes, {}, {quiet: true}); 188 | }, 189 | 190 | getJetpackJumpstartEnabled: function() { 191 | for(var i=0; i < JPS.jetpack.jumpstart_modules.length; i++) { 192 | var module = JPS.jetpack.jumpstart_modules[i]; 193 | if ( ! this.isJetpackModuleEnabled( module.slug ) ) { 194 | return false; 195 | } 196 | } 197 | return true; 198 | }, 199 | 200 | getLayout: function() { 201 | return layout; 202 | }, 203 | 204 | emitChange: function() { 205 | this.emit(CHANGE_EVENT); 206 | }, 207 | 208 | addChangeListener: function(callback) { 209 | this.on(CHANGE_EVENT, callback); 210 | }, 211 | 212 | removeChangeListener: function(callback) { 213 | this.removeListener(CHANGE_EVENT, callback); 214 | } 215 | }); 216 | 217 | // Register callback to handle all updates 218 | AppDispatcher.register(function(action) { 219 | 220 | switch(action.actionType) { 221 | case JPSConstants.SITE_SET_TYPE: 222 | setType(action.type); 223 | SiteStore.emitChange(); 224 | break; 225 | 226 | case JPSConstants.SITE_SET_TITLE: 227 | setTitle(action.title); 228 | SiteStore.emitChange(); 229 | break; 230 | 231 | case JPSConstants.SITE_SET_DESCRIPTION: 232 | setDescription(action.description); 233 | SiteStore.emitChange(); 234 | break; 235 | 236 | case JPSConstants.SITE_SAVE_TITLE_AND_DESCRIPTION: 237 | setTitle(action.title); 238 | setDescription(action.description); 239 | SiteStore.emitChange(); 240 | break; 241 | 242 | case JPSConstants.SITE_SET_THEME: 243 | setActiveTheme(action.themeId); 244 | SiteStore.emitChange(); 245 | break; 246 | 247 | case JPSConstants.SITE_INSTALL_THEME: 248 | installedTheme(action.theme); 249 | SiteStore.emitChange(); 250 | break; 251 | 252 | case JPSConstants.SITE_JETPACK_CONFIGURED: 253 | setJetpackConfigured(); 254 | SiteStore.emitChange(); 255 | break; 256 | 257 | case JPSConstants.SITE_JETPACK_ADD_MODULES: 258 | setJetpackAdditionalModules(action.modules); 259 | SiteStore.emitChange(); 260 | break; 261 | 262 | case JPSConstants.SITE_JETPACK_MODULE_ENABLED: 263 | setJetpackModuleActivated(action.slug); 264 | SiteStore.emitChange(); 265 | break; 266 | 267 | case JPSConstants.SITE_JETPACK_MODULE_DISABLED: 268 | setJetpackModuleDectivated(action.slug); 269 | SiteStore.emitChange(); 270 | break; 271 | 272 | case JPSConstants.SITE_JETPACK_JUMPSTART_ENABLED: 273 | setJetpackJumpstartActivated(); 274 | SiteStore.emitChange(); 275 | break; 276 | 277 | case JPSConstants.SITE_SET_LAYOUT: 278 | setLayout(action.layout); 279 | SiteStore.emitChange(); 280 | break; 281 | 282 | case JPSConstants.SITE_CREATE_CONTACT_US_PAGE: 283 | setContactUsPage(action.data); 284 | SiteStore.emitChange(); 285 | break; 286 | 287 | case JPSConstants.SITE_CREATE_LAYOUT_PAGES: 288 | setLayoutPages( action.data ); 289 | SiteStore.emitChange(); 290 | break; 291 | 292 | case JPSConstants.SITE_INSTALL_WOOCOMMERCE: 293 | setShopStatus(); 294 | SiteStore.emitChange(); 295 | break; 296 | 297 | case JPSConstants.SITE_INSTALL_WOOCOMMERCE_SUCCESS: 298 | setWooCommerceStatus(); 299 | SiteStore.emitChange(); 300 | break; 301 | 302 | case JPSConstants.SITE_REDIRECT_TO_WOOCOMMERCE_SETUP: 303 | setWooCommerceRedirectStatus(); 304 | SiteStore.emitChange(); 305 | break; 306 | 307 | default: 308 | // no op 309 | } 310 | }); 311 | 312 | module.exports = SiteStore; 313 | -------------------------------------------------------------------------------- /client/stores/spinner-store.js: -------------------------------------------------------------------------------- 1 | var AppDispatcher = require('../dispatcher/app-dispatcher'), 2 | EventEmitter = require('events').EventEmitter, 3 | JPSConstants = require('../constants/jetpack-onboarding-constants'); 4 | 5 | var CHANGE_EVENT = 'change'; 6 | 7 | var spinnerEnabled = false, 8 | spinnerMessage = null; 9 | 10 | function show(message) { 11 | spinnerEnabled = true; 12 | spinnerMessage = message; 13 | } 14 | 15 | function hide() { 16 | spinnerEnabled = false; 17 | spinnerMessage = null; 18 | } 19 | 20 | var SpinnerStore = _.extend({}, EventEmitter.prototype, { 21 | showing: function() { 22 | return spinnerEnabled; 23 | }, 24 | 25 | getMessage: function() { 26 | return spinnerMessage; 27 | }, 28 | 29 | addChangeListener: function(callback) { 30 | this.on( CHANGE_EVENT, callback ); 31 | }, 32 | 33 | removeChangeListener: function(callback) { 34 | this.removeListener( CHANGE_EVENT, callback ); 35 | }, 36 | 37 | emitChange: function() { 38 | this.emit( CHANGE_EVENT ); 39 | }, 40 | }); 41 | 42 | AppDispatcher.register(function(action) { 43 | 44 | switch(action.actionType) { 45 | case JPSConstants.SHOW_SPINNER: 46 | show(action.message); 47 | SpinnerStore.emitChange(); 48 | break; 49 | 50 | case JPSConstants.HIDE_SPINNER: 51 | hide(); 52 | SpinnerStore.emitChange(); 53 | break; 54 | 55 | default: 56 | // no op 57 | } 58 | }); 59 | 60 | module.exports = SpinnerStore; -------------------------------------------------------------------------------- /client/utils/wp-ajax.js: -------------------------------------------------------------------------------- 1 | /* 2 | * A simple wrapper for calls to WP's "ajaxurl". 3 | * 4 | * This exists because WP's wp_send_json_error doesn't actually send an error code, but rather 5 | * a 200 OK response with a structure like this: 6 | * {success: false, data: "something went wrong"} 7 | * 8 | * So this class smoothes the difference between 50x errors and WP's error object. 9 | * 10 | * For convenience, this returns a jQuery.Deferred object which can have .done() 11 | * and .fail() methods chained onto it, similar to jQuery.post's "success" and "fail" 12 | * 13 | * Also, it accepts an "action" param instead of a URL, since all WP ajax requests 14 | * actually go via the same URL with different parameters, and it invokes callbacks with 15 | * just the "data" portion of WP's ajax payload, rather than the whole structure. 16 | * 17 | **/ 18 | 19 | var DataActions = require('actions/data-actions'); 20 | 21 | var WPAjax = (function() { 22 | 23 | return { 24 | post: function(action, payload, options) { 25 | options = typeof options !== 'undefined' ? options : {}; 26 | payload = typeof payload !== 'undefined' ? payload : {}; 27 | var data = _.extend(payload, {action: action, nonce: JPS.nonce}); 28 | 29 | var deferred = jQuery.Deferred(); 30 | 31 | // passing quiet: true allows page navigation before this request has finished. 32 | // this is also handy when you're calling from within a Dispatch cycle, as it 33 | // no longer triggers an additional Dispatch (which would cause an error) 34 | if ( !options.quiet ) { 35 | DataActions.requestStarted(); 36 | } 37 | 38 | jQuery.post( ajaxurl, data ) 39 | .always( function () { 40 | if ( !options.quiet ) { 41 | DataActions.requestFinished(); 42 | } 43 | }) 44 | .success( function( response ) { 45 | if ( ! response.success ) { 46 | deferred.reject(response.data); 47 | } else { 48 | deferred.resolve(response.data); 49 | } 50 | }) 51 | .fail( function() { 52 | deferred.reject("Server error"); 53 | }); 54 | 55 | return deferred; 56 | } 57 | }; 58 | 59 | })(); 60 | 61 | module.exports = WPAjax; -------------------------------------------------------------------------------- /client/vendor.js: -------------------------------------------------------------------------------- 1 | require( 'react' ); 2 | require( 'react-dom' ); -------------------------------------------------------------------------------- /client/welcome-panel.jsx: -------------------------------------------------------------------------------- 1 | var React = require( 'react' ), 2 | ReactDOM = require( 'react-dom' ), 3 | WelcomeWidget = require( './components/page' ), 4 | Paths = require( './constants/jetpack-onboarding-paths' ), 5 | SetupProgressStore = require( 'stores/setup-progress-store' ); 6 | 7 | module.exports = function() { 8 | jQuery( document ).ready( function () { 9 | 10 | if ( ! document.getElementById( 'jpo-welcome-panel' ) ) { 11 | return; 12 | } 13 | 14 | SetupProgressStore.init( [ 15 | // NOTE: You can have "static: true" to include un-clickable 16 | // prefilled steps that act as though they've already been completed 17 | // { 18 | // name: "Sign up", 19 | // completed: true, 20 | // static: true 21 | // }, 22 | { 23 | name: 'Enable Jetpack', 24 | slug: Paths.JETPACK_MODULES_STEP_SLUG, 25 | neverSkip: true, // don't skip this even if it's been completed 26 | welcomeView: require('./components/steps/jetpack-jumpstart'), 27 | }, 28 | { 29 | name: 'Site title', 30 | slug: Paths.SITE_TITLE_STEP_SLUG, 31 | welcomeView: require('./components/steps/site-title'), 32 | }, 33 | { 34 | name: 'Is this a blog?', 35 | slug: Paths.IS_BLOG_STEP_SLUG, 36 | welcomeView: require('./components/steps/layout'), 37 | }, 38 | { 39 | name: 'Set your homepage', 40 | slug: Paths.HOMEPAGE_STEP_SLUG, 41 | welcomeView: require('./components/steps/homepage') 42 | }, 43 | { 44 | name: "Contact Info", 45 | slug: Paths.CONTACT_PAGE_STEP_SLUG, 46 | welcomeView: require('./components/steps/contact') 47 | }, 48 | { 49 | name: 'Business Address', 50 | slug: Paths.BUSINESS_ADDRESS_SLUG, 51 | welcomeView: require('./components/steps/business-address'), 52 | }, 53 | { 54 | name: 'WooCommerce', 55 | slug: Paths.WOOCOMMERCE_SLUG, 56 | welcomeView: require('./components/steps/woocommerce'), 57 | }, 58 | { 59 | name: "Review settings", 60 | slug: Paths.REVIEW_STEP_SLUG, 61 | welcomeView: require('./components/steps/review'), 62 | includeInProgress: false, 63 | neverSkip: true 64 | } 65 | ] ); 66 | 67 | ReactDOM.render( 68 | React.createElement( WelcomeWidget, {} ), document.getElementById( 'jpo-welcome-panel' ) 69 | ); 70 | } ); 71 | }; 72 | -------------------------------------------------------------------------------- /font/Genericons-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/jetpack-onboarding/06c6c51417b95d7b766ee5aad762769b9c208571/font/Genericons-Regular.otf -------------------------------------------------------------------------------- /font/genericons-regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/jetpack-onboarding/06c6c51417b95d7b766ee5aad762769b9c208571/font/genericons-regular-webfont.eot -------------------------------------------------------------------------------- /font/genericons-regular-webfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /font/genericons-regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/jetpack-onboarding/06c6c51417b95d7b766ee5aad762769b9c208571/font/genericons-regular-webfont.ttf -------------------------------------------------------------------------------- /font/genericons-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/jetpack-onboarding/06c6c51417b95d7b766ee5aad762769b9c208571/font/genericons-regular-webfont.woff -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | process.env.UV_THREADPOOL_SIZE = 100; // fix a bug in libsass 2 | 3 | var gulp = require( 'gulp' ); 4 | var sass = require( 'gulp-sass' ); 5 | var autoprefixer = require( 'gulp-autoprefixer' ); 6 | var sourcemaps = require( 'gulp-sourcemaps' ); 7 | var childProcess = require( 'child_process' ); 8 | 9 | function doSass() { 10 | if ( arguments.length ) { 11 | console.log( 'Sass file ' + arguments[0].path + ' changed.' ); 12 | } 13 | var start = new Date(); 14 | console.log( 'Building CSS bundle' ); 15 | gulp.src( './scss/welcome-panel.scss' ) 16 | .pipe( sass().on( 'error', sass.logError ) ) 17 | .pipe( autoprefixer() ) 18 | .pipe( sourcemaps.write( '.' ) ) 19 | .pipe( gulp.dest( './css' ) ) 20 | .on( 'end', function() { 21 | console.log( 'CSS finished.' ); 22 | } ); 23 | }; 24 | 25 | gulp.task( 'sass:build', function() { 26 | doSass(); 27 | } ); 28 | 29 | gulp.task( 'sass:watch', function() { 30 | doSass(); 31 | gulp.watch( [ './**/*.scss' ], doSass ); 32 | } ); 33 | 34 | gulp.task( 'webpack:build', function( cb ) { 35 | var env = process.env.NODE_ENV; 36 | var command = ( 'undefined' !== env && 'production' === 'env' ) 37 | ? 'webpack --color' 38 | : 'webpack -p --color'; 39 | 40 | childProcess.exec( command, function( err, stdout, stderr ) { 41 | console.log( stdout ); 42 | console.log( stderr ); 43 | cb( err ); 44 | } ); 45 | } ); 46 | 47 | gulp.task( 'webpack:watch', ( cb ) => { 48 | const webpack_watch = childProcess.spawn( 'webpack', [ '--watch', '--color' ] ); 49 | 50 | webpack_watch.stdout.on( 'data', ( data ) => { 51 | console.log( `stdout: ${data}` ); 52 | } ); 53 | 54 | webpack_watch.stderr.on( 'data', ( data ) => { 55 | console.log( `stderr: ${data}` ); 56 | } ); 57 | 58 | webpack_watch.on( 'close', ( code ) => { 59 | console.log( `child process exited with code ${code}` ); 60 | } ); 61 | } ); 62 | 63 | gulp.task( 'default', [ 'sass:build', 'webpack:build' ] ); 64 | gulp.task( 'watch', [ 'sass:watch', 'webpack:watch' ] ); 65 | -------------------------------------------------------------------------------- /img/feature-photon-sm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/jetpack-onboarding/06c6c51417b95d7b766ee5aad762769b9c208571/img/feature-photon-sm.jpg -------------------------------------------------------------------------------- /img/jpo-contact.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/jetpack-onboarding/06c6c51417b95d7b766ee5aad762769b9c208571/img/jpo-contact.jpg -------------------------------------------------------------------------------- /img/jpo-layout-news.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/jetpack-onboarding/06c6c51417b95d7b766ee5aad762769b9c208571/img/jpo-layout-news.jpg -------------------------------------------------------------------------------- /img/jpo-layout-static.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/jetpack-onboarding/06c6c51417b95d7b766ee5aad762769b9c208571/img/jpo-layout-static.jpg -------------------------------------------------------------------------------- /img/jpo-themes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/jetpack-onboarding/06c6c51417b95d7b766ee5aad762769b9c208571/img/jpo-themes.png -------------------------------------------------------------------------------- /img/jpo-welcome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/jetpack-onboarding/06c6c51417b95d7b766ee5aad762769b9c208571/img/jpo-welcome.png -------------------------------------------------------------------------------- /img/spinner-2x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/jetpack-onboarding/06c6c51417b95d7b766ee5aad762769b9c208571/img/spinner-2x.gif -------------------------------------------------------------------------------- /img/stats-example-sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/jetpack-onboarding/06c6c51417b95d7b766ee5aad762769b9c208571/img/stats-example-sm.png -------------------------------------------------------------------------------- /jetpack-onboarding.php: -------------------------------------------------------------------------------- 1 | 'administrator', 45 | 'fields' => array( 'id' ) ) 46 | ); 47 | 48 | $users = wp_list_pluck( $users_query, 'id' ); 49 | } 50 | 51 | foreach ( $users as $user_id ) { 52 | $setting = get_user_option( 'metaboxhidden_dashboard', $user_id ); 53 | 54 | if ( ! $setting || ! is_array( $setting ) ) { 55 | $setting = array(); 56 | } 57 | 58 | if ( in_array( Jetpack_Onboarding_WelcomePanel::DASHBOARD_WIDGET_ID, $setting ) ) { 59 | $setting = array_diff( $setting, array( Jetpack_Onboarding_WelcomePanel::DASHBOARD_WIDGET_ID ) ); 60 | update_user_option( $user_id, "metaboxhidden_dashboard", $setting, true ); 61 | } 62 | } 63 | } 64 | 65 | if ( defined( 'WP_CLI' ) && WP_CLI ) { 66 | WP_CLI::add_command( 'jpo reset', 'jpo_reset' ); 67 | } 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jetpack-onboarding", 3 | "version": "0.1.0", 4 | "scripts": { 5 | "build": "gulp", 6 | "build-production": "NODE_ENV=production gulp", 7 | "watch": "gulp watch", 8 | "lint": "eslint client --ext=js,jsx" 9 | }, 10 | "devDependencies": { 11 | "@automattic/custom-colors-loader": "automattic/custom-colors-loader", 12 | "@automattic/dops-components": "automattic/dops-components", 13 | "autoprefixer-loader": "^3.2.0", 14 | "babel": "^6.23.0", 15 | "babel-core": "^6.24.1", 16 | "babel-eslint": "^7.2.3", 17 | "babel-loader": "^7.0.0", 18 | "babel-plugin-add-module-exports": "^0.2.1", 19 | "babel-plugin-transform-es3-member-expression-literals": "^6.22.0", 20 | "babel-plugin-transform-runtime": "^6.23.0", 21 | "babel-preset-es2015": "^6.24.1", 22 | "babel-preset-react": "^6.24.1", 23 | "babel-preset-stage-1": "^6.24.1", 24 | "babel-runtime": "^6.23.0", 25 | "classnames": "^2.2.5", 26 | "css-loader": "^0.28.2", 27 | "es5-shim": "^4.5.9", 28 | "eslint": "^3.19.0", 29 | "eslint-loader": "^1.7.1", 30 | "eslint-plugin-react": "^7.0.1", 31 | "extract-text-webpack-plugin": "^2.1.0", 32 | "file-loader": "^0.11.1", 33 | "flux": "^3.1.2", 34 | "glob": "^7.1.2", 35 | "gulp": "^3.9.1", 36 | "gulp-autoprefixer": "^4.0.0", 37 | "gulp-sass": "^3.1.0", 38 | "gulp-sourcemaps": "^2.6.0", 39 | "history": "^4.6.1", 40 | "json-loader": "^0.5.4", 41 | "keymirror": "^0.1.1", 42 | "lodash": "^4.17.4", 43 | "merge-stream": "^1.0.1", 44 | "node-sass": "^4.5.3", 45 | "radium": "^0.19.1", 46 | "react": "^15.5.4", 47 | "react-dom": "^15.5.4", 48 | "react-router": "^4.1.1", 49 | "react-select": "^1.0.0-rc.5", 50 | "sass-loader": "^6.0.5", 51 | "style-loader": "^0.18.1", 52 | "url-loader": "^0.5.8", 53 | "webpack": "^2.6.1" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /scss/_business-address.scss: -------------------------------------------------------------------------------- 1 | .welcome__container { 2 | .welcome__business-address--form { 3 | display: flex; 4 | flex-wrap: wrap; 5 | justify-content: space-between; 6 | } 7 | 8 | input.welcome__business-address--input { 9 | max-width: 312px; 10 | margin-bottom: 16px; 11 | } 12 | 13 | input#business-address-1, 14 | input#business-address-2, 15 | input#business-name { 16 | max-width: 100%; 17 | } 18 | 19 | .welcome__button-container { 20 | width: 100%; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /scss/_contact.scss: -------------------------------------------------------------------------------- 1 | .welcome__contact { 2 | img { 3 | max-width: 320px; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /scss/_container.scss: -------------------------------------------------------------------------------- 1 | #jpo-welcome-panel { 2 | *, *:before, *:after { box-sizing: border-box; } 3 | 4 | position: fixed; 5 | top: 32px; 6 | left: 160px; 7 | bottom: 0; 8 | z-index: 999; 9 | width: calc( 100% - 160px ); 10 | overflow: auto; 11 | box-sizing: border-box; 12 | background-color: #f1f1f1; 13 | 14 | @media ( max-width: 960px ) { 15 | left: 36px; 16 | width: calc( 100% - 36px ); 17 | } 18 | 19 | @media ( max-width: 782px ) { 20 | top: 0; 21 | left: 0px; 22 | width: 100%; 23 | } 24 | } 25 | 26 | .welcome__wrapper { 27 | position: relative; 28 | background: #fff; 29 | border: 1px solid #ddd; 30 | margin: 10px; 31 | padding: 52px 16px; 32 | min-height: 600px; // fallback 33 | min-height: calc( 100vh - 66px ); // 46 + 20 34 | @include fancy-clearfix; 35 | 36 | @media ( min-width: 782px ) { 37 | margin: 20px; 38 | padding: 80px 16px; 39 | min-height: calc( 100vh - 72px ); // 32 + 40 40 | } 41 | button + button { 42 | margin-left: 20px; 43 | } 44 | img { 45 | width: 100%; 46 | max-width: 100%; 47 | } 48 | } 49 | 50 | .welcome__container { 51 | padding: 0 10px; 52 | width: 100%; 53 | max-width: 1024px; 54 | margin: 0 auto; 55 | } 56 | 57 | .welcome__content-box { 58 | background: white; 59 | margin: 10px; 60 | border: 1px solid #eee; 61 | } 62 | 63 | .welcome__debug { 64 | margin: 56px 0 0 10px; // Get debug bar below the admin bar + 10px 65 | position: relative; 66 | z-index: 11; 67 | 68 | @media ( min-width: 782px ) { 69 | margin: 10px 0 -10px 20px; 70 | } 71 | 72 | a.button { 73 | margin-left: 8px; 74 | margin-bottom: 0; 75 | 76 | &:first-child { 77 | margin-left: 0; 78 | } 79 | } 80 | } 81 | 82 | .welcome__loading-overlay { 83 | position: absolute; 84 | top: 0; 85 | left: 0; 86 | width: 100%; 87 | height: 100%; 88 | 89 | z-index: 10; 90 | background-color: rgba( white, 0.9 ); 91 | } 92 | 93 | .welcome__loading-message { 94 | position: absolute; 95 | left: 50%; 96 | top: 200px; 97 | padding: 20px; 98 | width: 50%; 99 | min-width: 300px; 100 | 101 | text-align: center; 102 | background-color: white; 103 | border-radius: 2px; 104 | border: 2px solid #888; 105 | 106 | transform: translate( -50%, 0 ); 107 | 108 | * { 109 | vertical-align: middle; 110 | } 111 | } 112 | 113 | .welcome__submit { 114 | margin-top: 32px; 115 | } 116 | 117 | img.welcome__loading-spinner { 118 | width: 16px; 119 | height: 16px; 120 | } 121 | 122 | #welcome__jetpack { 123 | .welcome__skip-step { 124 | margin-left: 10px; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /scss/_get-started.scss: -------------------------------------------------------------------------------- 1 | .welcome__container { 2 | .welcome__get-started--callout { 3 | margin-top: 8px; 4 | margin-bottom: 40px; 5 | 6 | @media ( max-width: 600px ) { 7 | margin-top: 0; 8 | margin-bottom: 24px; 9 | font-size: 13px; 10 | } 11 | } 12 | } 13 | 14 | img.welcome__get-started-image { 15 | width: 60vw; 16 | position: absolute; 17 | bottom: 0; 18 | left: 50%; 19 | transform: translateX( -50% ); 20 | 21 | // preventing the image from covering actions on smaller views 22 | @media ( max-width: 550px ), 23 | ( max-height: 750px ) { 24 | display: none; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /scss/_homepage.scss: -------------------------------------------------------------------------------- 1 | .welcome__homepage-cols { 2 | max-width: 780px; 3 | margin: 0 auto; 4 | display: flex; 5 | flex-flow: row wrap; 6 | align-items: flex-start; 7 | } 8 | 9 | .welcome__homepage-col { 10 | flex: 1; 11 | 12 | @media ( max-width: 700px ) { 13 | flex: 1 100%; 14 | margin-bottom: 15px; 15 | } 16 | 17 | img { 18 | max-width: 320px; 19 | height: auto; 20 | vertical-align: middle; 21 | } 22 | 23 | &.is-selected { 24 | img { 25 | box-shadow: 0 0 0 3px #bfe7f3; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /scss/_jetpack-jumpstart.scss: -------------------------------------------------------------------------------- 1 | .jetpack_connect_info { 2 | 3 | @media screen and ( max-width: 782px ) { 4 | width: 95%; 5 | margin: 0 5%; 6 | float: none; 7 | } 8 | 9 | text-align: left; 10 | float: left; 11 | width: 43%; 12 | margin: 0 3.5%; 13 | 14 | h2 { 15 | width: 100%; 16 | text-align: center; 17 | margin: 40px 0 20px 0; 18 | } 19 | 20 | img { 21 | width: 100%; 22 | float: left; 23 | margin: 0 0 20px 0; 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /scss/_review.scss: -------------------------------------------------------------------------------- 1 | .welcome__review-cols { 2 | display: flex; 3 | flex-flow: row wrap; 4 | align-items: flex-start; 5 | 6 | margin-top: 50px; 7 | 8 | @media ( max-width: 700px ) { 9 | margin-top: 0; 10 | } 11 | } 12 | 13 | .welcome__review-col { 14 | flex: 1; 15 | 16 | @media ( max-width: 700px ) { 17 | flex: 1 100%; 18 | margin-bottom: 15px; 19 | } 20 | 21 | .dashicons { 22 | color: $blue-wordpress; 23 | vertical-align: middle; 24 | } 25 | } 26 | 27 | .welcome__review-list { 28 | display: inline-block; 29 | text-align: left; 30 | 31 | ul { 32 | margin-top: 6px; 33 | margin-left: 30px; 34 | } 35 | } 36 | 37 | .welcome__dismiss { 38 | position: absolute; 39 | top: 8px; 40 | right: 16px; 41 | 42 | * { 43 | vertical-align: middle; 44 | } 45 | 46 | a { 47 | text-decoration: none; 48 | } 49 | } 50 | 51 | .welcome__review-cta { 52 | img { 53 | max-width: 320px; 54 | border: 1px solid #ccd8e1; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /scss/_site-title.scss: -------------------------------------------------------------------------------- 1 | #welcome__site-title .welcome__site-title--form { 2 | max-width: 400px; 3 | margin: 0 auto; 4 | text-align: left; 5 | @include fancy-clearfix; 6 | 7 | input { 8 | display: block; 9 | width: 100%; 10 | margin-bottom: 16px; 11 | } 12 | button { 13 | margin-top: 32px; 14 | } 15 | .welcome__submit { 16 | text-align: center; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /scss/_typography.scss: -------------------------------------------------------------------------------- 1 | .welcome__container { 2 | text-align: center; 3 | 4 | h1 { 5 | font-size: 32px; 6 | line-height: 1.2; 7 | font-weight: 400; 8 | padding-right: 0; 9 | margin-bottom: 0; 10 | 11 | @media ( max-width: 600px ) { 12 | font-size: 20px; 13 | } 14 | } 15 | 16 | h4 { 17 | margin-bottom: 10px; 18 | border-bottom: 1px #ddd solid; 19 | } 20 | 21 | h5 { 22 | margin-top: 0; 23 | font-size: 1em; 24 | font-weight: 400; 25 | } 26 | 27 | input, 28 | select { 29 | margin: 1; 30 | padding: 3px 5px; 31 | } 32 | 33 | input[type="text"] { 34 | width: 100%; 35 | box-shadow: none; 36 | background: #fff; 37 | border: 1px solid #d5d5d5; 38 | padding: 8px 10px; 39 | } 40 | 41 | .submit { 42 | background-color: #EEE; 43 | text-align: right; 44 | } 45 | 46 | .welcome__callout { 47 | margin-top: 8px; 48 | margin-bottom: 40px; 49 | color: #777; 50 | font-size: 18px; 51 | line-height: 1.5; 52 | 53 | @media ( max-width: 600px ) { 54 | margin-top: 0; 55 | margin-bottom: 24px; 56 | font-size: 13px; 57 | } 58 | } 59 | 60 | .welcome__skip-link { 61 | color: #bbb; 62 | font-size: 90%; 63 | margin-left: 20px; 64 | border-bottom: 1px dotted #bbb; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /scss/_woocommerce.scss: -------------------------------------------------------------------------------- 1 | .welcome__container { 2 | .welcome__woocommerce--install-container { 3 | margin-bottom: 20px; 4 | 5 | .welcome__woocommerce--checkbox { 6 | float: none; 7 | margin: 1px 8px 0 0; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /scss/color-overrides.scss: -------------------------------------------------------------------------------- 1 | 2 | // Grays 3 | $gray: darken( #ccc, 20% ); 4 | 5 | $gray-light: #f7f7f7; 6 | $gray-dark: #555; 7 | 8 | $white: #fff; 9 | 10 | // Calypso Blues -> Jetpack Greens (yes, blue variables are green now…) 11 | $blue-light: #008ec2; 12 | $blue-medium: #0085ba; 13 | $blue-dark: #006799; 14 | 15 | $blue-wordpress: #0073AA; 16 | 17 | // Alerts 18 | $alert-yellow: #f0b849; 19 | $alert-red: #d94f4f; 20 | $alert-green: #4ab866; 21 | -------------------------------------------------------------------------------- /scss/mixins/_breakpoint.scss: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Breakpoint Mixin 3 | // See https://wpcalypso.wordpress.com/devdocs/docs/coding-guidelines/css.md#media-queries 4 | // ========================================================================== 5 | 6 | $breakpoints: 480px, 660px, 960px, 1040px; // Think very carefully before adding a new breakpoint 7 | 8 | @mixin breakpoint( $size ){ 9 | @if type-of($size) == string { 10 | $approved-value: 0; 11 | @each $breakpoint in $breakpoints { 12 | $and-larger: ">" + $breakpoint; 13 | $and-smaller: "<" + $breakpoint; 14 | 15 | @if $size == $and-smaller { 16 | $approved-value: 1; 17 | @media ( max-width: $breakpoint ) { 18 | @content; 19 | } 20 | } 21 | @else { 22 | @if $size == $and-larger { 23 | $approved-value: 2; 24 | @media ( min-width: $breakpoint + 1 ) { 25 | @content; 26 | } 27 | } 28 | @else { 29 | @each $breakpoint-end in $breakpoints { 30 | $range: $breakpoint + "-" + $breakpoint-end; 31 | @if $size == $range { 32 | $approved-value: 3; 33 | @media ( min-width: $breakpoint + 1 ) and ( max-width: $breakpoint-end ) { 34 | @content; 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | @if $approved-value == 0 { 42 | $sizes: ""; 43 | @each $breakpoint in $breakpoints { 44 | $sizes: $sizes + " " + $breakpoint; 45 | } 46 | // TODO - change this to use @error, when it is supported by node-sass 47 | @warn "ERROR in breakpoint( #{ $size } ): You can only use these sizes[ #{$sizes} ] using the following syntax [ <#{ nth( $breakpoints, 1 ) } >#{ nth( $breakpoints, 1 ) } #{ nth( $breakpoints, 1 ) }-#{ nth( $breakpoints, 2 ) } ]"; 48 | } 49 | } 50 | @else { 51 | $sizes: ""; 52 | @each $breakpoint in $breakpoints { 53 | $sizes: $sizes + " " + $breakpoint; 54 | } 55 | // TODO - change this to use @error, when it is supported by node-sass 56 | @warn "ERROR in breakpoint( #{ $size } ): Please wrap the breakpoint $size in parenthesis. You can use these sizes[ #{$sizes} ] using the following syntax [ <#{ nth( $breakpoints, 1 ) } >#{ nth( $breakpoints, 1 ) } #{ nth( $breakpoints, 1 ) }-#{ nth( $breakpoints, 2 ) } ]"; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /scss/mixins/_calypso-forms.scss: -------------------------------------------------------------------------------- 1 | @import '../color-overrides'; 2 | 3 | // Below, you can choose from either using global form styles or class-driven 4 | // form styles. By default, the global styles are on. 5 | 6 | %clear-text { 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | %form { 12 | ul { 13 | margin: 0; 14 | padding: 0; 15 | list-style: none; 16 | } 17 | } 18 | 19 | %form-field { 20 | margin: 0; 21 | padding: 7px 14px; 22 | width: 100%; 23 | color: $gray-dark; 24 | font-size: 16px; 25 | line-height: 1.5; 26 | border: 1px solid lighten( $gray, 20% ); 27 | background-color: $white; 28 | transition: all .15s ease-in-out; 29 | box-sizing: border-box; 30 | 31 | &::placeholder { 32 | color: $gray; 33 | } 34 | 35 | &:hover { 36 | border-color: lighten( $gray, 10% ); 37 | } 38 | 39 | &:focus { 40 | border-color: $blue-wordpress; 41 | outline: none; 42 | box-shadow: 0 0 0 2px $blue-light; 43 | 44 | &::-ms-clear { 45 | display: none; 46 | } 47 | } 48 | 49 | &:disabled { 50 | background: $gray-light; 51 | border-color: lighten( $gray, 30% ); 52 | color: lighten( $gray, 10% ); 53 | 54 | &:hover { 55 | cursor: default; 56 | } 57 | 58 | &::placeholder { 59 | color: lighten( $gray, 10% ); 60 | } 61 | } 62 | } 63 | 64 | %textarea { 65 | min-height: 92px; 66 | } 67 | 68 | 69 | // ========================================================================== 70 | // Global form elements 71 | // ========================================================================== 72 | 73 | form { 74 | @extend %form; 75 | } 76 | input[type="text"], 77 | input[type="search"], 78 | input[type="email"], 79 | input[type="number"], 80 | input[type="password"], 81 | input[type=checkbox], 82 | input[type=radio], 83 | input[type="tel"], 84 | input[type="url"], 85 | textarea { 86 | @extend %form-field; 87 | } 88 | textarea { 89 | @extend %textarea; 90 | } 91 | 92 | fieldset, 93 | input[type="text"], 94 | input[type="search"], 95 | input[type="email"], 96 | input[type="number"], 97 | input[type="password"], 98 | input[type="tel"], 99 | input[type="url"], 100 | textarea, 101 | select, 102 | label { 103 | box-sizing: border-box; 104 | } 105 | 106 | /*Checkbooms*/ 107 | 108 | input[type=checkbox], 109 | input[type=radio] { 110 | clear: none; 111 | cursor: pointer; 112 | display: inline-block; 113 | line-height: 0; 114 | height: 16px; 115 | margin: 4px 0 0; 116 | float: left; 117 | outline: 0; 118 | padding: 0; 119 | text-align: center; 120 | vertical-align: middle; 121 | width: 16px; 122 | min-width: 16px; 123 | appearance: none; 124 | } 125 | 126 | input[type=checkbox] + span, 127 | input[type=radio] + span { 128 | display: block; 129 | margin-left: 24px; 130 | } 131 | 132 | input[type=checkbox] { 133 | &:checked:before { 134 | @extend %clear-text; 135 | 136 | content: '\f147'; 137 | margin: -4px 0 0 -5px; 138 | float: left; 139 | display: inline-block; 140 | vertical-align: middle; 141 | width: 16px; 142 | font: 400 23px/1 Dashicons; 143 | speak: none; 144 | color: $blue-medium; 145 | } 146 | &:disabled:checked:before { 147 | color: lighten( $gray, 10% ); 148 | } 149 | } 150 | 151 | input[type=radio] { 152 | border-radius: 50%; 153 | margin-right: 4px; 154 | line-height: 10px; 155 | 156 | &:checked:before { 157 | float: left; 158 | display: inline-block; 159 | content: '\f159'; 160 | margin: 3px; 161 | width: 8px; 162 | height: 8px; 163 | text-indent: -9999px; 164 | background: $blue-medium; 165 | vertical-align: middle; 166 | border-radius: 50%; 167 | animation: grow .2s ease-in-out; 168 | } 169 | 170 | &:disabled:checked:before { 171 | background: lighten( $gray, 30% ); 172 | } 173 | } 174 | 175 | @keyframes grow { 176 | 0% { 177 | transform: scale(0.3); 178 | } 179 | 180 | 60% { 181 | transform: scale(1.15); 182 | } 183 | 184 | 100% { 185 | transform: scale(1); 186 | } 187 | } 188 | 189 | @keyframes grow { 190 | 0% { 191 | transform: scale(0.3); 192 | } 193 | 194 | 60% { 195 | transform: scale(1.15); 196 | } 197 | 198 | 100% { 199 | transform: scale(1); 200 | } 201 | } 202 | 203 | /* end checkbooms */ 204 | 205 | 206 | // ========================================================================== 207 | // Custom form elements 208 | // ========================================================================== 209 | 210 | // Tristate checkbox for bulk selection options 211 | 212 | // Example: 213 | // 214 | // Check all 215 | // 216 | 217 | .checkbox-tristate { 218 | @extend input[type=checkbox]; 219 | position: relative; 220 | margin: 20px 0 19px 20px; 221 | 222 | &:before { 223 | position: absolute; 224 | color: $blue-medium; 225 | font-family: Dashicons; 226 | } 227 | 228 | .some-selected & { 229 | &:before { 230 | content: '\f460'; // .noticon-minimize 231 | top: 7px; 232 | left: 0; 233 | } 234 | } 235 | .all-selected & { 236 | &:before { 237 | content: url("data:image/svg+xml;utf8,"); 238 | height: 100%; 239 | width: 100%; 240 | top: 0; 241 | } 242 | } 243 | } 244 | 245 | 246 | // Toggle switches 247 | .toggle[type="checkbox"] { 248 | display: none; 249 | } 250 | 251 | .toggle { 252 | + .toggle-label { 253 | position: relative; 254 | display: inline-block; 255 | border-radius: 12px; 256 | padding: 2px; 257 | width: 40px; 258 | height: 24px; 259 | background: lighten( $gray, 10% ); 260 | vertical-align: middle; 261 | outline: 0; 262 | cursor: pointer; 263 | transition: all .4s ease; 264 | 265 | &:after, &:before{ 266 | position: relative; 267 | display: block; 268 | content: ""; 269 | width: 20px; 270 | height: 20px; 271 | } 272 | &:after{ 273 | left: 0; 274 | border-radius: 50%; 275 | background: #fff; 276 | transition: all .2s ease; 277 | } 278 | &:before{ 279 | display: none; 280 | } 281 | &:hover{ 282 | background: lighten( $gray, 20% ); 283 | } 284 | } 285 | &:focus{ 286 | + .toggle-label{ 287 | box-shadow: 0 0 0 2px $blue-medium; 288 | } 289 | &:checked + .toggle-label{ 290 | box-shadow: 0 0 0 2px $blue-light; 291 | } 292 | } 293 | &:checked{ 294 | + .toggle-label{ 295 | background: $blue-medium; 296 | 297 | &:after{ 298 | left: 16px; 299 | } 300 | } 301 | } 302 | &:checked:hover{ 303 | + .toggle-label{ 304 | background: $blue-light; 305 | } 306 | } 307 | 308 | &:disabled, 309 | &:disabled:hover { 310 | + .toggle-label{ 311 | background: lighten( $gray, 30% ); 312 | } 313 | } 314 | } 315 | 316 | // Classes for toggle state before action is complete (updating plugin or something) 317 | .toggle.is-toggling { 318 | + .toggle-label { 319 | background: $blue-medium; 320 | } 321 | &:checked { 322 | + .toggle-label { 323 | background: lighten( $gray, 20% ); 324 | } 325 | } 326 | } 327 | 328 | select { 329 | background: $white url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiAgICAgICAgPHRpdGxlPmFycm93LWRvd248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4gICAgICAgIDxnIGlkPSJhcnJvdy1kb3duIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjQzhEN0UxIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNSw2IEwxNyw3LjUgTDEwLjI1LDE0LjI1IEwzLjUsNy41IEw1LDYgTDEwLjI1LDExLjI1IEwxNS41LDYgWiIgaWQ9IkRvd24tQXJyb3ciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==) no-repeat right 10px center; 330 | border-color: lighten( $gray, 20% ); 331 | border-style: solid; 332 | border-radius: 4px; 333 | border-width: 1px 1px 2px; 334 | color: $gray-dark; 335 | cursor: pointer; 336 | display: inline-block; 337 | margin: 0; 338 | outline: 0; 339 | overflow: hidden; 340 | font-size: 14px; 341 | line-height: 21px; 342 | font-weight: 600; 343 | text-overflow: ellipsis; 344 | text-decoration: none; 345 | vertical-align: top; 346 | white-space: nowrap; 347 | box-sizing: border-box; 348 | padding: 7px 32px 9px 14px; // Aligns the text to the 8px baseline grid and adds padding on right to allow for the arrow. 349 | -webkit-appearance: none; 350 | -moz-appearance: none; 351 | appearance: none; 352 | 353 | &:hover { 354 | background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiAgICAgICAgPHRpdGxlPmFycm93LWRvd248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4gICAgICAgIDxnIGlkPSJhcnJvdy1kb3duIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjYThiZWNlIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNSw2IEwxNyw3LjUgTDEwLjI1LDE0LjI1IEwzLjUsNy41IEw1LDYgTDEwLjI1LDExLjI1IEwxNS41LDYgWiIgaWQ9IkRvd24tQXJyb3ciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==); 355 | } 356 | 357 | &:focus { 358 | background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiA8dGl0bGU+YXJyb3ctZG93bjwvdGl0bGU+IDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPiA8ZGVmcz48L2RlZnM+IDxnIGlkPSJQYWdlLTEiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHNrZXRjaDp0eXBlPSJNU1BhZ2UiPiA8ZyBpZD0iYXJyb3ctZG93biIgc2tldGNoOnR5cGU9Ik1TQXJ0Ym9hcmRHcm91cCIgZmlsbD0iIzJlNDQ1MyI+IDxwYXRoIGQ9Ik0xNS41LDYgTDE3LDcuNSBMMTAuMjUsMTQuMjUgTDMuNSw3LjUgTDUsNiBMMTAuMjUsMTEuMjUgTDE1LjUsNiBaIiBpZD0iRG93bi1BcnJvdyIgc2tldGNoOnR5cGU9Ik1TU2hhcGVHcm91cCI+PC9wYXRoPiA8L2c+IDwvZz48L3N2Zz4=); 359 | border-color: $blue-medium; 360 | box-shadow: 0 0 0 2px $blue-light; 361 | outline: 0; 362 | -moz-outline:none; 363 | -moz-user-focus:ignore; 364 | } 365 | 366 | &:disabled, 367 | &:hover:disabled { 368 | background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiAgICAgICAgPHRpdGxlPmFycm93LWRvd248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4gICAgICAgIDxnIGlkPSJhcnJvdy1kb3duIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjZTllZmYzIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNSw2IEwxNyw3LjUgTDEwLjI1LDE0LjI1IEwzLjUsNy41IEw1LDYgTDEwLjI1LDExLjI1IEwxNS41LDYgWiIgaWQ9IkRvd24tQXJyb3ciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==) no-repeat right 10px center;; 369 | } 370 | 371 | // A smaller variant that works well when presented inline with text 372 | &.is-compact { 373 | min-width: 0; 374 | padding: 0 20px 2px 6px; 375 | margin: 0 4px; 376 | background-position: right 5px center; 377 | background-size: 12px 12px; 378 | } 379 | 380 | // Make it display:block when it follows a label 381 | label &, 382 | label + & { 383 | display: block; 384 | min-width: 200px; 385 | 386 | &.is-compact { 387 | display: inline-block; 388 | min-width: 0; 389 | } 390 | } 391 | 392 | // IE: Remove the default arrow 393 | &::-ms-expand { 394 | display: none; 395 | } 396 | 397 | // IE: Remove default background and color styles on focus 398 | &::-ms-value { 399 | background: none; 400 | color: $gray-dark; 401 | } 402 | 403 | // Firefox: Remove the focus outline, see http://stackoverflow.com/questions/3773430/remove-outline-from-select-box-in-ff/18853002#18853002 404 | &:-moz-focusring { 405 | color: transparent; 406 | text-shadow: 0 0 0 $gray-dark; 407 | } 408 | } 409 | 410 | /*Search Inputs*/ 411 | input[type="search"]::-webkit-search-decoration { 412 | // We don't use the native results="" UI 413 | // Ensures the input text is flush to the start of the element, as in regular text inputs 414 | // See, for example, http://geek.michaelgrace.org/2011/06/webkit-search-input-styling/ 415 | display: none; 416 | } 417 | -------------------------------------------------------------------------------- /scss/mixins/_clearfix.scss: -------------------------------------------------------------------------------- 1 | 2 | // Clearfix 3 | @mixin clearfix() { 4 | content: ""; 5 | display: table; 6 | } 7 | 8 | // Clear after (not all clearfix need this also) 9 | @mixin clearfix-after() { 10 | clear: both; 11 | } 12 | 13 | // Fancy clearfix 14 | // didn't want to mess with the other clearfixes just yet - MJA 15 | @mixin fancy-clearfix() { 16 | &:after { 17 | content: "."; 18 | display: block; 19 | height: 0; 20 | clear: both; 21 | visibility: hidden; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /scss/welcome-panel.scss: -------------------------------------------------------------------------------- 1 | $light-gray: #ccc; 2 | $very-light-gray: #f5f5f5; 3 | 4 | @import 'mixins/breakpoint'; 5 | @import 'mixins/clearfix'; 6 | @import 'color-overrides'; 7 | 8 | .welcome__container { 9 | @import 'mixins/calypso-forms'; 10 | } 11 | 12 | @import 'typography'; 13 | @import 'container'; 14 | 15 | // Step-specific style 16 | @import 'get-started'; 17 | @import 'site-title'; 18 | @import 'homepage'; 19 | @import 'contact'; 20 | @import 'review'; 21 | @import 'jetpack-jumpstart'; 22 | @import 'business-address'; 23 | @import 'woocommerce'; 24 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require( 'webpack' ), 2 | fs = require( 'fs' ), 3 | path = require( 'path' ), 4 | ExtractTextPlugin = require( 'extract-text-webpack-plugin' ); 5 | 6 | var NODE_ENV = process.env.NODE_ENV || 'development'; 7 | 8 | // build the plugin list, optionally excluding hot update plugins if we're building for production 9 | var plugins = [ 10 | new webpack.optimize.CommonsChunkPlugin( { name: 'vendor', filename: 'vendor.bundle.js' } ), 11 | new ExtractTextPlugin( '[name].css' ), 12 | new webpack.DefinePlugin( { 13 | 'process.env': { 14 | // This has effect on the react lib size 15 | NODE_ENV: JSON.stringify( NODE_ENV ) 16 | } 17 | } ) 18 | ]; 19 | 20 | module.exports = { 21 | // progress: true, 22 | entry: { 23 | 'jetpack-onboarding': './client/jetpack-onboarding.js', 24 | 'ie-shims': './client/ie-shims.js', 25 | 'vendor': [ 'react', 'react-dom' ] 26 | }, 27 | output: { 28 | publicPath: '/assets/', 29 | path: path.resolve( __dirname, 'dist' ), 30 | filename: '[name].js', 31 | chunkFilename: '[id].js' 32 | }, 33 | resolve: { 34 | extensions: [ '.js', '.jsx' ], 35 | alias: { 36 | stores: path.join( __dirname, '/client/stores' ), 37 | actions: path.join( __dirname, '/client/actions' ), 38 | }, 39 | modules: [ 40 | path.resolve( __dirname, 'client' ), 41 | fs.realpathSync( path.join( __dirname, 'node_modules/@automattic/dops-components/client' ) ), 42 | "node_modules" 43 | ] 44 | }, 45 | node: { 46 | fs: 'empty' 47 | }, 48 | stats: { colors: true, reasons: true }, 49 | module: { 50 | rules: [ 51 | { 52 | test: /\.jsx?$/, 53 | use: [ 54 | require.resolve( 'babel-loader' ), 55 | { 56 | loader: require.resolve( 'eslint-loader' ), 57 | options: { 58 | configFile: path.join( __dirname, '.eslintrc' ), 59 | quiet: true 60 | } 61 | } 62 | ], 63 | 64 | // include both typical npm-linked locations and default module locations to handle both cases 65 | include: [ 66 | path.join( __dirname, 'test' ), 67 | path.join( __dirname, 'client' ), 68 | fs.realpathSync( path.join( __dirname, 'node_modules/@automattic/dops-components/client' ) ), 69 | path.join( __dirname, 'node_modules/@automattic/dops-components/client' ) 70 | ] 71 | }, 72 | { 73 | test: /\.json$/, 74 | use: require.resolve( 'json-loader' ) 75 | }, 76 | { 77 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 78 | use: require.resolve( 'url-loader' ) + '?limit=20000&mimetype=image/svg+xml' 79 | }, 80 | { 81 | test: /\.css$/, 82 | use: ExtractTextPlugin.extract( { 83 | use: ['css-loader', 'autoprefixer-loader' ] 84 | } ), 85 | }, 86 | { 87 | test: /\.scss$/, 88 | use: ExtractTextPlugin.extract( { 89 | fallback: "style-loader", 90 | use: [ 91 | 'css-loader', 92 | 'autoprefixer-loader', 93 | 'sass-loader', 94 | { 95 | loader: '@automattic/custom-colors-loader', 96 | options: { 97 | file: path.join( __dirname, './scss/color-overrides.scss' ) 98 | } 99 | } 100 | ] 101 | } ), 102 | }, 103 | ] 104 | }, 105 | plugins: plugins 106 | }; 107 | --------------------------------------------------------------------------------