From c35effe369784eae94ee520390cf3cea003eb43e Mon Sep 17 00:00:00 2001 From: squigglybob Date: Wed, 18 Dec 2024 11:34:27 +0000 Subject: [PATCH] add module step to wizard (#2617) * chore: remove translations attribute from controls * feat: correctly toggle checked state of checkboxes * feat: add confirm step * feat: add the real modules into the data set * feat: split use case selection out of modules also make multiple use cases selectable * fix: padding on toggle buttons * feat: select modules according to use case chosen * feat: remember options selected if navigating back to use case page * feat: update modules on api call * feat: add intro and celebration steps * fix: use empty to test if module is enabled/disabled * fix: use a discord link with no expiration or limits --- dt-core/admin/admin-settings-endpoints.php | 25 +++ .../components/setup-wizard-celebration.js | 48 +++++ .../admin/components/setup-wizard-controls.js | 1 - .../admin/components/setup-wizard-intro.js | 51 +++++ .../admin/components/setup-wizard-modules.js | 189 ++++++++---------- .../admin/components/setup-wizard-plugins.js | 5 +- .../components/setup-wizard-use-cases.js | 176 ++++++++++++++++ dt-core/admin/components/setup-wizard.js | 10 +- dt-core/admin/js/dt-shared.js | 9 + dt-core/admin/menu/menu-setup-wizard.php | 84 +++----- .../configuration/config-site-defaults.php | 15 -- 11 files changed, 428 insertions(+), 185 deletions(-) create mode 100644 dt-core/admin/components/setup-wizard-celebration.js create mode 100644 dt-core/admin/components/setup-wizard-intro.js create mode 100644 dt-core/admin/components/setup-wizard-use-cases.js diff --git a/dt-core/admin/admin-settings-endpoints.php b/dt-core/admin/admin-settings-endpoints.php index 4f2fbb246..8d56b7cc2 100644 --- a/dt-core/admin/admin-settings-endpoints.php +++ b/dt-core/admin/admin-settings-endpoints.php @@ -223,6 +223,31 @@ public function add_api_routes() { 'permission_callback' => [ $this, 'default_permission_check' ], ] ); + + register_rest_route( + $this->namespace, '/modules-update', [ + 'methods' => 'POST', + 'callback' => [ $this, 'update_modules' ], + 'permission_callback' => [ $this, 'default_permission_check' ], + ] + ); + } + + public function update_modules( WP_REST_REQUEST $request ) { + $modules_option_name = 'dt_post_type_modules'; + $modules_to_update = $request->get_param( 'modules' ); + $modules_to_update = dt_recursive_sanitize_array( $modules_to_update ); + + $modules = dt_get_option( $modules_option_name ); + + foreach ( $modules as $key => $module ) { + if ( array_key_exists( $key, $modules_to_update ) ) { + $modules[$key]['enabled'] = !empty( $modules_to_update[$key] ) ? true : false; + } + } + + update_option( $modules_option_name, $modules ); + return true; } public function update_languages( WP_REST_REQUEST $request ) { diff --git a/dt-core/admin/components/setup-wizard-celebration.js b/dt-core/admin/components/setup-wizard-celebration.js new file mode 100644 index 000000000..b68e7b21f --- /dev/null +++ b/dt-core/admin/components/setup-wizard-celebration.js @@ -0,0 +1,48 @@ +import { html } from 'https://cdn.jsdelivr.net/gh/lit/dist@2.4.0/all/lit-all.min.js'; +import { OpenLitElement } from './setup-wizard-open-element.js'; + +export class SetupWizardCelebration extends OpenLitElement { + static get properties() { + return { + step: { type: Object }, + }; + } + + back() { + this.dispatchEvent(new CustomEvent('back')); + } + next() { + window.location.href = window.setupWizardShare.admin_url; + } + + render() { + return html` +
+

All finished

+
+

+ You can change all of these choices in the Settings (D.T.) tab in + the WP admin. +

+

+ D.T. has a huge ability to be customized to serve your needs. If you + have any questions or need any further assistance in getting D.T. to + work for you, please reach out on our community forum or our discord + channel. +

+ + Community Forum + + + Discord Invitation + +
+ +
+ `; + } +} +customElements.define('setup-wizard-celebration', SetupWizardCelebration); diff --git a/dt-core/admin/components/setup-wizard-controls.js b/dt-core/admin/components/setup-wizard-controls.js index 7d0f74b62..6bf031a69 100644 --- a/dt-core/admin/components/setup-wizard-controls.js +++ b/dt-core/admin/components/setup-wizard-controls.js @@ -4,7 +4,6 @@ import { OpenLitElement } from './setup-wizard-open-element.js'; export class SetupWizardControls extends OpenLitElement { static get properties() { return { - translations: { type: Object }, hideBack: { type: Boolean }, backLabel: { type: String }, nextLabel: { type: String }, diff --git a/dt-core/admin/components/setup-wizard-intro.js b/dt-core/admin/components/setup-wizard-intro.js new file mode 100644 index 000000000..cdb6fd995 --- /dev/null +++ b/dt-core/admin/components/setup-wizard-intro.js @@ -0,0 +1,51 @@ +import { html } from 'https://cdn.jsdelivr.net/gh/lit/dist@2.4.0/all/lit-all.min.js'; +import { OpenLitElement } from './setup-wizard-open-element.js'; + +export class SetupWizardIntro extends OpenLitElement { + static get properties() { + return { + step: { type: Object }, + }; + } + + back() { + this.dispatchEvent(new CustomEvent('back')); + } + next() { + this.dispatchEvent(new CustomEvent('next')); + } + + render() { + return html` +
+

Setting up D.T. for you

+
+

+ We're glad you are here, and want to help set you up so you can take + advantage of the powertool that is D.T. +

+

+ D.T. can be used in many ways from managing connections and + relationships, all the way through to tracking and managing a + movement of Disciple Making. +

+

+ In order to help you, we want to take you through a series of + choices to give you the best start at getting Disiple.Tools setup + ready to suit your needs. +

+

+ Feel free to skip this setup stage if you already know what you + need. But if this will be helpful for you let's dive in. +

+
+ +
+ `; + } +} +customElements.define('setup-wizard-intro', SetupWizardIntro); diff --git a/dt-core/admin/components/setup-wizard-modules.js b/dt-core/admin/components/setup-wizard-modules.js index 54a9ef7fe..a17d5d4e9 100644 --- a/dt-core/admin/components/setup-wizard-modules.js +++ b/dt-core/admin/components/setup-wizard-modules.js @@ -9,12 +9,10 @@ export class SetupWizardModules extends OpenLitElement { static get properties() { return { step: { type: Object }, - firstStep: { type: Boolean }, stage: { type: String, attribute: false }, - useCases: { type: Array, attribute: false }, - option: { type: Object, attribute: false }, availableModules: { type: Array, attribute: false }, - selectedModules: { type: Array, attribute: false }, + selectedModules: { type: Object, attribute: false }, + loading: { Boolean, attribute: false }, }; } @@ -22,24 +20,34 @@ export class SetupWizardModules extends OpenLitElement { super(); this.stage = 'work'; this.data = window.setupWizardShare.data; - this.useCases = []; - this.option = {}; - this.availableModules = []; - this.selectedModules = []; - } + this.translations = window.setupWizardShare.translations; + this.loading = false; + this.availableModules = Object.entries(this.data.modules) + .map(([key, module]) => { + return { + key, + ...module, + }; + }) + .reduce((modules, module) => { + if (module.locked) { + return modules; + } + modules.push(module); + return modules; + }, []); + this.selectedModules = this.availableModules.reduce((modules, module) => { + modules[module.key] = false; + return modules; + }, {}); - firstUpdated() { - /* Reduce the keys down to ones that exist in the details list of use cases */ - const useCaseKeys = this.step.config.reduce((keys, key) => { - if (this.data.use_cases[key]) { - return [...keys, key]; + Object.entries(this.data.use_cases).forEach(([key, useCase]) => { + if (useCase.selected) { + useCase.recommended_modules.forEach((moduleKey) => { + this.selectedModules[moduleKey] = true; + }); } - return keys; - }, []); - this.useCases = useCaseKeys.map( - (useCaseKey) => this.data.use_cases[useCaseKey], - ); - this.availableModules = this.data.modules; + }); } back() { @@ -48,20 +56,14 @@ export class SetupWizardModules extends OpenLitElement { this.stage = 'work'; break; case 'work': - this.stage = 'prompt'; - break; - case 'prompt': this.dispatchEvent(new CustomEvent('back')); break; } } - next() { + async next() { switch (this.stage) { - case 'prompt': - this.stage = 'work'; - break; case 'work': - /* TODO: fire off to the API here */ + await this.submitModuleChanges(); this.stage = 'follow-up'; break; case 'follow-up': @@ -69,116 +71,99 @@ export class SetupWizardModules extends OpenLitElement { break; } } - selectOption(option) { - this.option = option; - this.selectedModules = option.recommended_modules; + nextLabel() { + return this.translations.next; } toggleModule(key) { - if (this.selectedModules.includes(key)) { - const index = this.selectedModules.findIndex((module) => module === key); - this.selectedModules = [ - ...this.selectedModules.slice(0, index), - ...this.selectedModules.slice(index + 1), - ]; + const checkbox = this.renderRoot.querySelector(`#${key}`); + if (this.selectedModules[key]) { + checkbox.checked = false; + this.selectedModules[key] = false; } else { - this.selectedModules = [...this.selectedModules, key]; + checkbox.checked = true; + this.selectedModules[key] = true; } } + async submitModuleChanges() { + this.loading = true; + this.requestUpdate(); + await window.dt_admin_shared.modules_update(this.selectedModules); + this.loading = false; + } render() { return html`
- ${this.stage === 'prompt' - ? html` -

Time to customize what fields are available.

-

- In the next step you will be able to choose between some - common use cases of Disciple.Tools -

-

- You will still be able to customize to your particular use - case. -

- ` - : ''} ${this.stage === 'work' ? html` -

Choose a use case

+

Module selection

- Choose one of these use cases to tailor what parts of - Disciple.Tools to turn on. + The recommended modules for your chosen use case(s) are + selected below

- You can fine tune those choices further to your own needs. + Feel free to change this selection according to what you need + D.T to do.

-
-
- ${this.useCases && this.useCases.length > 0 - ? this.useCases.map( - (option) => html` - - `, - ) - : ''} -
-
-

Available Modules

-
- ${Object.keys(this.availableModules).length > 0 - ? html` -
+ + + + + + + + + + ${Object.keys(this.availableModules).length > 0 + ? html` ${repeat( this.availableModules, (module) => module.key, (module) => { return html` - + + this.toggleModule(module.key)} + > + + + + `; }, )} - - ` - : ''} - + ` + : ''} + +
ModuleDescription
+ + ${module.name}${module.description}
+ ${this.loading ? html`` : ''} ` : ''} ${this.stage === 'follow-up' ? html` -

Your choices have been implemented

+

The modules you have chosen have been turned on.

- You can make further changes to the way D.T. works in the - 'Settings (DT)' section of the Wordpress admin. + You can adjust these modules to your liking in the 'Settings + (DT)' section of the Wordpress admin.

` : ''}
diff --git a/dt-core/admin/components/setup-wizard-plugins.js b/dt-core/admin/components/setup-wizard-plugins.js index e5910c779..1311a4b15 100644 --- a/dt-core/admin/components/setup-wizard-plugins.js +++ b/dt-core/admin/components/setup-wizard-plugins.js @@ -74,11 +74,12 @@ export class SetupWizardPlugins extends OpenLitElement { Plugin Name Install/Activate
- this.select_all()} - >select all + select all + Description Info diff --git a/dt-core/admin/components/setup-wizard-use-cases.js b/dt-core/admin/components/setup-wizard-use-cases.js new file mode 100644 index 000000000..b6e998db5 --- /dev/null +++ b/dt-core/admin/components/setup-wizard-use-cases.js @@ -0,0 +1,176 @@ +import { + html, + repeat, +} from 'https://cdn.jsdelivr.net/gh/lit/dist@2.4.0/all/lit-all.min.js'; +import { OpenLitElement } from './setup-wizard-open-element.js'; + +export class SetupWizardUseCases extends OpenLitElement { + static get properties() { + return { + step: { type: Object }, + firstStep: { type: Boolean }, + stage: { type: String, attribute: false }, + useCases: { type: Array, attribute: false }, + options: { type: Object, attribute: false }, + availableModules: { type: Array, attribute: false }, + selectedModules: { type: Array, attribute: false }, + }; + } + + constructor() { + super(); + this.stage = 'work'; + this.data = window.setupWizardShare.data; + this.translations = window.setupWizardShare.translations; + this.availableModules = []; + this.selectedModules = []; + this.options = Object.entries(this.data.use_cases).reduce( + (options, [key, useCase]) => { + const selected = + (useCase.selected && useCase.selected === true) || false; + return { + ...options, + [key]: selected, + }; + }, + {}, + ); + } + + firstUpdated() { + /* Reduce the keys down to ones that exist in the details list of use cases */ + const useCaseKeys = this.step.config.reduce((keys, key) => { + if (this.data.use_cases[key]) { + return [...keys, key]; + } + return keys; + }, []); + this.useCases = useCaseKeys.map( + (useCaseKey) => this.data.use_cases[useCaseKey], + ); + } + + back() { + switch (this.stage) { + case 'follow-up': + this.stage = 'work'; + break; + case 'work': + this.stage = 'prompt'; + break; + case 'prompt': + this.dispatchEvent(new CustomEvent('back')); + break; + } + } + next() { + switch (this.stage) { + case 'prompt': + this.stage = 'work'; + break; + case 'work': + this.saveOptions(); + this.stage = 'follow-up'; + break; + case 'follow-up': + this.dispatchEvent(new CustomEvent('next')); + break; + } + } + nextLabel() { + switch (this.stage) { + case 'work': + return this.translations.confirm; + default: + return this.translations.next; + } + } + toggleOption(event) { + const option = event.target.id; + if (this.options[option]) { + this.options[option] = false; + } else { + this.options[option] = true; + } + } + saveOptions() { + for (const option in this.options) { + window.setupWizardShare.data.use_cases[option].selected = + this.options[option]; + } + } + + render() { + return html` +
+
+ ${this.stage === 'prompt' + ? html` +

Time to customize what fields are available.

+

+ In the next step you will be able to choose between some + common use cases of Disciple.Tools +

+

+ You will still be able to customize to your particular use + case. +

+ ` + : ''} + ${this.stage === 'work' + ? html` +

Choose a use case

+

+ Choose one of these use cases to tailor what parts of + Disciple.Tools to turn on. +

+

+ You can fine tune those choices further to your own needs. +

+
+
+ ${this.useCases && this.useCases.length > 0 + ? this.useCases.map( + (option) => html` + + `, + ) + : ''} +
+
+ ` + : ''} + ${this.stage === 'follow-up' + ? html` +

Use cases selected

+

+ Now that you have chosen your use cases, we can recommend some + modules and plugins that will be helpful for these use cases. +

+ ` + : ''} +
+ +
+ `; + } +} +customElements.define('setup-wizard-use-cases', SetupWizardUseCases); diff --git a/dt-core/admin/components/setup-wizard.js b/dt-core/admin/components/setup-wizard.js index 67071dda9..b1e1ad71f 100644 --- a/dt-core/admin/components/setup-wizard.js +++ b/dt-core/admin/components/setup-wizard.js @@ -209,17 +209,19 @@ export class SetupWizard extends LitElement { } .toggle { position: relative; - display: inline-block; + display: inline-flex; + cursor: pointer; input { display: none; } div { display: inline-block; - padding-block: 1rem; + padding: 1rem 0.5rem; background-color: var(--default-color); border-radius: 8px; width: 100%; text-align: center; + transition: all 120ms linear; } input:checked + div { background-color: var(--secondary-color); @@ -327,8 +329,8 @@ export class SetupWizard extends LitElement { render() { return html`
+

${this.translations.title}

-

${this.translations.title}

${this.isKitchenSink ? this.kitchenSink() : html` ${this.renderStep()} `} @@ -442,8 +444,6 @@ export class SetupWizard extends LitElement { `; } - renderControls() {} - kitchenSink() { return html`
diff --git a/dt-core/admin/js/dt-shared.js b/dt-core/admin/js/dt-shared.js index 4641fb1b6..f6fc0192f 100644 --- a/dt-core/admin/js/dt-shared.js +++ b/dt-core/admin/js/dt-shared.js @@ -75,6 +75,15 @@ window.dt_admin_shared = { }, `dt-admin-settings/`, ), + modules_update: (modules) => + makeRequest( + 'POST', + 'modules-update', + { + modules, + }, + `dt-admin-settings/`, + ), }; jQuery(function ($) { diff --git a/dt-core/admin/menu/menu-setup-wizard.php b/dt-core/admin/menu/menu-setup-wizard.php index a16681bb5..8a2184576 100644 --- a/dt-core/admin/menu/menu-setup-wizard.php +++ b/dt-core/admin/menu/menu-setup-wizard.php @@ -19,6 +19,7 @@ class DT_Setup_Wizard { private static $_instance = null; + private $root = 'setup-wizard'; public static function instance() { if ( is_null( self::$_instance ) ) { self::$_instance = new self(); @@ -43,19 +44,25 @@ public function __construct() { public function enqueue_scripts(){ dt_theme_enqueue_script( 'setup-wizard', 'dt-core/admin/components/setup-wizard.js', [], true ); dt_theme_enqueue_script( 'setup-wizard-open-element', 'dt-core/admin/components/setup-wizard-open-element.js', [ 'setup-wizard' ], true ); + dt_theme_enqueue_script( 'setup-wizard-use-cases', 'dt-core/admin/components/setup-wizard-use-cases.js', [ 'setup-wizard', 'setup-wizard-open-element' ], true ); dt_theme_enqueue_script( 'setup-wizard-modules', 'dt-core/admin/components/setup-wizard-modules.js', [ 'setup-wizard', 'setup-wizard-open-element' ], true ); dt_theme_enqueue_script( 'setup-wizard-plugins', 'dt-core/admin/components/setup-wizard-plugins.js', [ 'setup-wizard', 'setup-wizard-open-element' ], true ); dt_theme_enqueue_script( 'setup-wizard-details', 'dt-core/admin/components/setup-wizard-details.js', [ 'setup-wizard', 'setup-wizard-open-element' ], true ); dt_theme_enqueue_script( 'setup-wizard-controls', 'dt-core/admin/components/setup-wizard-controls.js', [ 'setup-wizard', 'setup-wizard-open-element' ], true ); + dt_theme_enqueue_script( 'setup-wizard-intro', 'dt-core/admin/components/setup-wizard-intro.js', [ 'setup-wizard', 'setup-wizard-open-element' ], true ); + dt_theme_enqueue_script( 'setup-wizard-celebration', 'dt-core/admin/components/setup-wizard-celebration.js', [ 'setup-wizard', 'setup-wizard-open-element' ], true ); wp_localize_script( 'setup-wizard', 'setupWizardShare', [ 'translations' => [ 'title' => esc_html__( 'Disciple.Tools Setup Wizard', 'disciple_tools' ), 'next' => esc_html__( 'Next', 'disciple_tools' ), + 'submit' => esc_html__( 'Submit', 'disciple_tools' ), + 'confirm' => esc_html__( 'Confirm', 'disciple_tools' ), 'back' => esc_html__( 'Back', 'disciple_tools' ), ], 'steps' => $this->setup_wizard_steps(), 'data' => $this->setup_wizard_data(), + 'admin_url' => admin_url(), ] ); } @@ -109,9 +116,13 @@ public function setup_wizard_steps() { $bloginfo = get_bloginfo(); $steps = [ [ - 'key' => 'choose_your_path', - 'name' => 'Choose your path', - 'component' => 'setup-wizard-modules', + 'key' => 'intro', + 'component' => 'setup-wizard-intro', + ], + [ + 'key' => 'choose_your_use_cases', + 'name' => 'Choose your use cases', + 'component' => 'setup-wizard-use-cases', 'description' => 'How are you planning to use DT?', 'config' => [ 'media', @@ -120,37 +131,20 @@ public function setup_wizard_steps() { ] ], [ - 'name' => 'Site details', - 'description' => 'Fill in some site details', - 'component' => 'setup-wizard-details', - 'config' => [ - [ - 'type' => 'options', - 'options' => [ - [ - 'key' => 'blogname', - 'name' => 'Site name', - 'value' => isset( $bloginfo['name'] ) ? $bloginfo['name'] : '', - ], - [ - 'key' => 'blogdescription', - 'name' => 'Site description', - 'value' => isset( $bloginfo['description'] ) ? $bloginfo['description'] : '', - ], - [ - 'key' => 'admin_email', - 'name' => 'Admin email', - 'value' => isset( $bloginfo['admin_email'] ) ? $bloginfo['admin_email'] : '', - ] - ], - ] - ], + 'key' => 'choose_your_modules', + 'name' => 'Choose your modules', + 'component' => 'setup-wizard-modules', + 'description' => 'What modules do you want to use?', ], [ 'name' => 'Plugins', 'description' => 'Choose which plugins to install.', 'component' => 'setup-wizard-plugins', ], + [ + 'key' => 'celebration', + 'component' => 'setup-wizard-celebration', + ] ]; $steps = apply_filters( 'dt_setup_wizard_steps', $steps ); @@ -189,6 +183,7 @@ public static function get_plugins_list(){ public function setup_wizard_data() : array { + $modules = dt_get_option( 'dt_post_type_modules' ); $plugin_data = self::get_plugins_list(); $data = [ 'use_cases' => [ @@ -236,38 +231,7 @@ public function setup_wizard_data() : array { ], ], ], - 'modules' => [ - [ - 'key' => 'ipsum', - 'name' => 'Ipsum', - 'description' => 'Track who is ipsuming who', - 'details' => [ - 'fields' => [], - 'tiles' => [], - 'workflows' => [], - ], - ], - [ - 'key' => 'dolor', - 'name' => 'Dolor', - 'description' => 'Track who has been dolored when, and by who', - ], - [ - 'key' => 'lorem', - 'name' => 'lorem', - 'description' => 'Track the lorem status of your contacts', - ], - [ - 'key' => 'foo', - 'name' => 'Foo', - 'description' => 'Track contacts: who is fooing up with who; reminders of who needs fooing up with', - ], - [ - 'key' => 'bar', - 'name' => 'Bar', - 'description' => 'Track bars of people and their lorem status', - ], - ], + 'modules' => $modules, 'plugins' => $plugin_data, ]; diff --git a/dt-core/configuration/config-site-defaults.php b/dt-core/configuration/config-site-defaults.php index feec9d936..8b8a9e656 100644 --- a/dt-core/configuration/config-site-defaults.php +++ b/dt-core/configuration/config-site-defaults.php @@ -135,8 +135,6 @@ function dt_get_option( string $name ) { } return get_option( 'dt_site_options' ); - break; - case 'dt_site_custom_lists': $default_custom_lists = dt_get_site_custom_lists(); @@ -154,7 +152,6 @@ function dt_get_option( string $name ) { } //return apply_filters( "dt_site_custom_lists", get_option( 'dt_site_custom_lists' ) ); return get_option( 'dt_site_custom_lists' ); - break; case 'dt_field_customizations': return get_option( 'dt_field_customizations', [ @@ -192,8 +189,6 @@ function dt_get_option( string $name ) { else { return get_option( 'dt_base_user' ); } - break; - case 'location_levels': $default_levels = dt_get_location_levels(); @@ -216,7 +211,6 @@ function dt_get_option( string $name ) { $levels = get_option( 'dt_location_levels' ); } return $levels['location_levels']; - break; case 'auto_location': $setting = get_option( 'dt_auto_location' ); if ( false === $setting ) { @@ -224,8 +218,6 @@ function dt_get_option( string $name ) { $setting = get_option( 'dt_auto_location' ); } return $setting; - break; - case 'dt_storage_connection_id': return get_option( 'dt_storage_connection_id', '' ); @@ -235,8 +227,6 @@ function dt_get_option( string $name ) { update_option( 'dt_email_base_subject', 'Disciple.Tools' ); } return $subject_base; - break; - case 'dt_email_base_address_reply_to': return get_option( 'dt_email_base_address_reply_to', '' ); case 'dt_email_base_address': @@ -282,7 +272,6 @@ function dt_get_option( string $name ) { default: return false; - break; } } @@ -313,15 +302,11 @@ function dt_update_option( $name, $value, $autoload = false ) { $levels = wp_parse_args( $levels, $default_levels ); return update_option( 'dt_location_levels', $levels, $autoload ); - - break; case 'auto_location': return update_option( 'dt_auto_location', $value, $autoload ); - break; default: return false; - break; } }