├── .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 |
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 | Not now
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 |
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 | Next Step →
69 |
70 |
71 | );
72 | },
73 |
74 | _renderWithoutContactPage: function() {
75 | return (
76 |
77 |
Build a starter "Contact Us" page?
78 |
79 |
80 | Yes
81 | No Thanks
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 | Business
55 | Personal
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 |
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 | Next Step
78 |
79 |
80 | :
81 |
85 | { this.state.jetpackConnecting ? 'Connecting' : 'Connect' } to WordPress.com
86 |
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 | Yes
47 | Nope
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 |
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 |
{ JPS.steps.advanced_settings.cta_button_text }
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 |
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 |
67 |
68 | );
69 | },
70 |
71 | renderAlreadyInstalled: function() {
72 | return (
73 |
74 |
WooCommerce is ready to go
75 |
76 | Setup your store
77 | Not right now
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 |
--------------------------------------------------------------------------------