diff --git a/includes/class-blocks.php b/includes/class-blocks.php index eccc0fafa8..24e4b19dcf 100644 --- a/includes/class-blocks.php +++ b/includes/class-blocks.php @@ -40,7 +40,7 @@ public static function enqueue_block_editor_assets() { [ 'has_newsletters' => class_exists( 'Newspack_Newsletters_Subscription' ), 'has_reader_activation' => Reader_Activation::is_enabled(), - 'newsletters_url' => Wizards::get_wizard( 'engagement' )->newsletters_settings_url(), + 'newsletters_url' => Wizards::get_wizard( 'newsletters' )->newsletters_settings_url(), 'has_google_oauth' => Google_OAuth::is_oauth_configured(), 'google_logo_svg' => file_get_contents( dirname( NEWSPACK_PLUGIN_FILE ) . '/src/blocks/reader-registration/icons/google.svg' ), 'reader_activation_terms' => Reader_Activation::get_setting( 'terms_text' ), diff --git a/includes/class-newspack.php b/includes/class-newspack.php index 7e8efc3c0e..2c5c80e899 100644 --- a/includes/class-newspack.php +++ b/includes/class-newspack.php @@ -147,6 +147,10 @@ private function includes() { // Advertising Wizard. include_once NEWSPACK_ABSPATH . 'includes/wizards/advertising/class-advertising-display-ads.php'; include_once NEWSPACK_ABSPATH . 'includes/wizards/advertising/class-advertising-sponsors.php'; + + // Audience Wizard. + include_once NEWSPACK_ABSPATH . 'includes/wizards/audience/class-audience-configuration.php'; + include_once NEWSPACK_ABSPATH . 'includes/wizards/audience/class-audience-campaigns.php'; // Network Wizard. include_once NEWSPACK_ABSPATH . 'includes/wizards/class-network-wizard.php'; diff --git a/includes/class-wizards.php b/includes/class-wizards.php index ebb04d532b..5c06fa3cc3 100644 --- a/includes/class-wizards.php +++ b/includes/class-wizards.php @@ -55,6 +55,8 @@ public static function init() { ), 'advertising-display-ads' => new Advertising_Display_Ads(), 'advertising-sponsors' => new Advertising_Sponsors(), + 'audience-configuration' => new Audience_Configuration(), + 'audience-campaigns' => new Audience_Campaigns(), 'listings' => new Listings_Wizard(), 'network' => new Network_Wizard(), 'newsletters' => new Newsletters_Wizard(), diff --git a/includes/reader-activation/class-reader-activation.php b/includes/reader-activation/class-reader-activation.php index 06abbcf2e6..cff9842dc8 100644 --- a/includes/reader-activation/class-reader-activation.php +++ b/includes/reader-activation/class-reader-activation.php @@ -507,7 +507,7 @@ public static function get_prerequisites_status() { 'description' => __( 'Connect to your ESP to register readers with their email addresses and send newsletters.', 'newspack-plugin' ), 'instructions' => __( 'Connect to your email service provider (ESP) and enable at least one subscription list.', 'newspack-plugin' ), 'help_url' => 'https://help.newspack.com/engagement/reader-activation-system', - 'href' => \admin_url( '/admin.php?page=newspack-engagement-wizard#/newsletters' ), + 'href' => \admin_url( 'edit.php?post_type=newspack_nl_cpt&page=newspack-newsletters' ), 'action_text' => __( 'ESP settings' ), ], 'emails' => [ @@ -536,7 +536,7 @@ public static function get_prerequisites_status() { 'description' => __( 'Connecting to a Google reCAPTCHA account enables enhanced anti-spam for all Newspack sign-up blocks.', 'newspack-plugin' ), 'instructions' => __( 'Enable reCAPTCHA and enter your account credentials.', 'newspack-plugin' ), 'help_url' => 'https://help.newspack.com/engagement/reader-activation-system', - 'href' => \admin_url( '/admin.php?page=newspack-connections-wizard&scrollTo=recaptcha' ), + 'href' => \admin_url( '/admin.php?page=newspack-settings&scrollTo=newspack-settings-recaptcha' ), 'action_text' => __( 'reCAPTCHA settings' ), ], 'reader_revenue' => [ @@ -550,6 +550,7 @@ public static function get_prerequisites_status() { 'description' => __( 'Setting suggested donation amounts is required for enabling a streamlined donation experience.', 'newspack-plugin' ), 'instructions' => __( 'Set platform to "Newspack" or "News Revenue Hub" and configure your default donation settings. If using News Revenue Hub, set an Organization ID and a Donor Landing Page in News Revenue Hub Settings.', 'newspack-plugin' ), 'help_url' => 'https://help.newspack.com/engagement/reader-activation-system', + // @TODO: Update when platform is added. 'href' => \admin_url( '/admin.php?page=newspack-reader-revenue-wizard' ), 'action_text' => __( 'Reader Revenue settings' ), ], @@ -562,7 +563,7 @@ public static function get_prerequisites_status() { 'label' => __( 'Reader Activation Campaign', 'newspack-plugin' ), 'description' => __( 'Building a set of prompts with default segments and settings allows for an improved experience optimized for Reader Activation.', 'newspack-plugin' ), 'help_url' => 'https://help.newspack.com/engagement/reader-activation-system', - 'href' => self::is_ras_campaign_configured() ? \admin_url( '/admin.php?page=newspack-popups-wizard#/campaigns' ) : \admin_url( '/admin.php?page=newspack-engagement-wizard#/reader-activation/campaign' ), + 'href' => self::is_ras_campaign_configured() ? admin_url( '/admin.php?page=newspack-audience-campaigns' ) : admin_url( '/admin.php?page=newspack-audience-configuration#/campaign' ), 'action_enabled' => self::is_ras_ready_to_configure(), 'action_text' => __( 'Reader Activation campaign', 'newspack-plugin' ), 'disabled_text' => __( 'Waiting for all settings to be ready', 'newspack-plugin' ), diff --git a/includes/wizards/audience/class-audience-campaigns.php b/includes/wizards/audience/class-audience-campaigns.php new file mode 100644 index 0000000000..123414cb6a --- /dev/null +++ b/includes/wizards/audience/class-audience-campaigns.php @@ -0,0 +1,66 @@ +is_wizard_page() ) { + return; + } + + parent::enqueue_scripts_and_styles(); + + wp_enqueue_script( 'newspack-wizards' ); + } + + /** + * Add Audience top-level and Campaigns subpage to the /wp-admin menu. + */ + public function add_page() { + add_submenu_page( + $this->parent_slug, + $this->get_name(), + esc_html__( 'Campaigns', 'newspack-plugin' ), + $this->capability, + $this->slug, + [ $this, 'render_wizard' ] + ); + } +} diff --git a/includes/wizards/audience/class-audience-configuration.php b/includes/wizards/audience/class-audience-configuration.php new file mode 100644 index 0000000000..0f360d9ffd --- /dev/null +++ b/includes/wizards/audience/class-audience-configuration.php @@ -0,0 +1,320 @@ +is_wizard_page() ) { + return; + } + parent::enqueue_scripts_and_styles(); + $data = [ + 'has_memberships' => class_exists( 'WC_Memberships' ), + 'reader_activation_url' => admin_url( 'admin.php?page=newspack-audience-configuration#/' ), + 'esp_metadata_fields' => Reader_Activation\Sync\Metadata::get_default_fields(), + ]; + + if ( method_exists( 'Newspack\Newsletters\Subscription_Lists', 'get_add_new_url' ) ) { + $data['new_subscription_lists_url'] = Newsletters\Subscription_Lists::get_add_new_url(); + } + + if ( method_exists( 'Newspack_Newsletters_Subscription', 'get_lists' ) ) { + $data['available_newsletter_lists'] = Newspack_Newsletters_Subscription::get_lists(); + } + + $newspack_popups = Configuration_Managers::configuration_manager_class_for_plugin_slug( 'newspack-popups' ); + + if ( $newspack_popups->is_configured() ) { + $data['preview_query_keys'] = $newspack_popups->preview_query_keys(); + $data['preview_post'] = $newspack_popups->preview_post(); + $data['preview_archive'] = $newspack_popups->preview_archive(); + } + + $data['is_skipped_campaign_setup'] = get_option( static::SKIP_CAMPAIGN_SETUP_OPTION, '' ); + + wp_enqueue_script( 'newspack-wizards' ); + + wp_localize_script( + 'newspack-wizards', + 'newspackAudienceConfiguration', + $data + ); + } + + /** + * Add Audience top-level and Configuration subpage to the /wp-admin menu. + */ + public function add_page() { + // svg source - https://wphelpers.dev/icons/people + // SVG generated via https://boxy-svg.com/ with path width/height 20px. + $icon = 'data:image/svg+xml;base64,' . base64_encode( + ' + +' + ); + add_menu_page( + $this->get_name(), + __( 'Audience', 'newspack-plugin' ), + $this->capability, + $this->slug, + [ $this, 'render_wizard' ], + $icon + ); + add_submenu_page( + $this->slug, + $this->get_name(), + __( 'Configuration', 'newspack-plugin' ), + $this->capability, + $this->slug, + [ $this, 'render_wizard' ] + ); + } + + /** + * Register the endpoints needed for the wizard screens. + */ + public function register_api_endpoints() { + register_rest_route( + NEWSPACK_API_NAMESPACE, + '/wizard/' . $this->slug . '/reader-activation', + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'api_get_reader_activation_settings' ], + 'permission_callback' => [ $this, 'api_permissions_check' ], + ] + ); + register_rest_route( + NEWSPACK_API_NAMESPACE, + '/wizard/' . $this->slug . '/reader-activation', + [ + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => [ $this, 'api_update_reader_activation_settings' ], + 'permission_callback' => [ $this, 'api_permissions_check' ], + ] + ); + register_rest_route( + NEWSPACK_API_NAMESPACE, + '/wizard/' . $this->slug . '/reader-activation/activate', + [ + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => [ $this, 'api_activate_reader_activation' ], + 'permission_callback' => [ $this, 'api_permissions_check' ], + ] + ); + register_rest_route( + NEWSPACK_API_NAMESPACE, + '/wizard/' . $this->slug . '/reader-activation/skip-campaign-setup', + [ + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => function( $request ) { + $skip = $request->get_param( 'skip' ); + $skip_campaign_setup = update_option( static::SKIP_CAMPAIGN_SETUP_OPTION, $skip ); + return rest_ensure_response( + [ + 'skipped' => $skip, + 'updated' => $skip_campaign_setup, + ] + ); + }, + 'permission_callback' => [ $this, 'api_permissions_check' ], + ] + ); + } + + /** + * Get reader activation settings. + * + * @return WP_REST_Response + */ + public function api_get_reader_activation_settings() { + return rest_ensure_response( + [ + 'config' => Reader_Activation::get_settings(), + 'prerequisites_status' => Reader_Activation::get_prerequisites_status(), + 'memberships' => self::get_memberships_settings(), + 'can_esp_sync' => Reader_Activation\ESP_Sync::can_esp_sync( true ), + ] + ); + } + + /** + * Update reader activation settings. + * + * @param WP_REST_Request $request Request object. + * + * @return WP_REST_Response + */ + public function api_update_reader_activation_settings( $request ) { + $args = $request->get_params(); + foreach ( $args as $key => $value ) { + Reader_Activation::update_setting( $key, $value ); + } + + // Update Memberships options. + if ( isset( $args['memberships_require_all_plans'] ) ) { + Memberships::set_require_all_plans_setting( (bool) $args['memberships_require_all_plans'] ); + } + + // Update Memberships options. + if ( isset( $args['memberships_show_on_subscription_tab'] ) ) { + Memberships::set_show_on_subscription_tab_setting( (bool) $args['memberships_show_on_subscription_tab'] ); + } + + return rest_ensure_response( + [ + 'config' => Reader_Activation::get_settings(), + 'prerequisites_status' => Reader_Activation::get_prerequisites_status(), + 'memberships' => self::get_memberships_settings(), + 'can_esp_sync' => Reader_Activation\ESP_Sync::can_esp_sync( true ), + ] + ); + } + + /** + * Activate reader activation and publish RAS prompts/segments. + * + * @param WP_REST_Request $request WP Rest Request object. + * @return WP_REST_Response + */ + public function api_activate_reader_activation( WP_REST_Request $request ) { + $skip_activation = $request->get_param( 'skip_activation' ) ?? false; + $response = $skip_activation ? true : Reader_Activation::activate(); + + if ( is_wp_error( $response ) ) { + return new WP_REST_Response( [ 'message' => $response->get_error_message() ], 400 ); + } + + if ( true === $response ) { + Reader_Activation::update_setting( 'enabled', true ); + } + + return rest_ensure_response( $response ); + } + + /** + * Get memberships settings. + * + * @return array + */ + private static function get_memberships_settings() { + return [ + 'edit_gate_url' => Memberships::get_edit_gate_url(), + 'gate_status' => get_post_status( Memberships::get_gate_post_id() ), + 'plans' => Memberships::get_plans(), + 'require_all_plans' => Memberships::get_require_all_plans_setting(), + 'show_on_subscription_tab' => Memberships::get_show_on_subscription_tab_setting(), + ]; + } + + /** + * Parent file filter. Used to determine active menu items. + * + * @param string $parent_file Parent file to be overridden. + * @return string + */ + public function parent_file( $parent_file ) { + global $pagenow, $typenow; + + $cpts = [ + Memberships::GATE_CPT, + Emails::POST_TYPE, + ]; + + if ( in_array( $pagenow, [ 'post.php', 'post-new.php' ] ) && in_array( $typenow, $cpts ) ) { + return $this->slug; + } + + return $parent_file; + } + + /** + * Submenu file filter. Used to determine active submenu items. + * + * @param string $submenu_file Submenu file to be overridden. + * @return string + */ + public function submenu_file( $submenu_file ) { + global $pagenow, $typenow; + + $cpts = [ + Memberships::GATE_CPT, + Emails::POST_TYPE, + ]; + + if ( in_array( $pagenow, [ 'post.php', 'post-new.php' ] ) && in_array( $typenow, $cpts ) ) { + return $this->slug; + } + + return $submenu_file; + } +} diff --git a/includes/wizards/class-engagement-wizard.php b/includes/wizards/class-engagement-wizard.php index f7e6bdcf77..2738d5873a 100644 --- a/includes/wizards/class-engagement-wizard.php +++ b/includes/wizards/class-engagement-wizard.php @@ -85,51 +85,6 @@ public function register_api_endpoints() { 'sanitize_callback' => 'sanitize_text_field', ] ); - register_rest_route( - NEWSPACK_API_NAMESPACE, - '/wizard/' . $this->slug . '/reader-activation', - [ - 'methods' => \WP_REST_Server::READABLE, - 'callback' => [ $this, 'api_get_reader_activation_settings' ], - 'permission_callback' => [ $this, 'api_permissions_check' ], - ] - ); - register_rest_route( - NEWSPACK_API_NAMESPACE, - '/wizard/' . $this->slug . '/reader-activation', - [ - 'methods' => \WP_REST_Server::EDITABLE, - 'callback' => [ $this, 'api_update_reader_activation_settings' ], - 'permission_callback' => [ $this, 'api_permissions_check' ], - ] - ); - register_rest_route( - NEWSPACK_API_NAMESPACE, - '/wizard/' . $this->slug . '/reader-activation/activate', - [ - 'methods' => \WP_REST_Server::EDITABLE, - 'callback' => [ $this, 'api_activate_reader_activation' ], - 'permission_callback' => [ $this, 'api_permissions_check' ], - ] - ); - register_rest_route( - NEWSPACK_API_NAMESPACE, - '/wizard/' . $this->slug . '/reader-activation/skip-campaign-setup', - [ - 'methods' => \WP_REST_Server::EDITABLE, - 'callback' => function( $request ) { - $skip = $request->get_param( 'skip' ); - $skip_campaign_setup = update_option( static::SKIP_CAMPAIGN_SETUP_OPTION, $skip ); - return rest_ensure_response( - [ - 'skipped' => $skip, - 'updated' => $skip_campaign_setup, - ] - ); - }, - 'permission_callback' => [ $this, 'api_permissions_check' ], - ] - ); $meta_pixel = new Meta_Pixel(); register_rest_route( @@ -192,91 +147,6 @@ public function register_api_endpoints() { ); } - /** - * Get memberships settings. - * - * @return array - */ - private static function get_memberships_settings() { - return [ - 'edit_gate_url' => Memberships::get_edit_gate_url(), - 'gate_status' => \get_post_status( Memberships::get_gate_post_id() ), - 'plans' => Memberships::get_plans(), - 'require_all_plans' => Memberships::get_require_all_plans_setting(), - 'show_on_subscription_tab' => Memberships::get_show_on_subscription_tab_setting(), - ]; - } - - /** - * Get reader activation settings. - * - * @return WP_REST_Response - */ - public function api_get_reader_activation_settings() { - return rest_ensure_response( - [ - 'config' => Reader_Activation::get_settings(), - 'prerequisites_status' => Reader_Activation::get_prerequisites_status(), - 'memberships' => self::get_memberships_settings(), - 'can_esp_sync' => Reader_Activation\ESP_Sync::can_esp_sync( true ), - ] - ); - } - - /** - * Update reader activation settings. - * - * @param WP_REST_Request $request Request object. - * - * @return WP_REST_Response - */ - public function api_update_reader_activation_settings( $request ) { - $args = $request->get_params(); - foreach ( $args as $key => $value ) { - Reader_Activation::update_setting( $key, $value ); - } - - // Update Memberships options. - if ( isset( $args['memberships_require_all_plans'] ) ) { - Memberships::set_require_all_plans_setting( (bool) $args['memberships_require_all_plans'] ); - } - - // Update Memberships options. - if ( isset( $args['memberships_show_on_subscription_tab'] ) ) { - Memberships::set_show_on_subscription_tab_setting( (bool) $args['memberships_show_on_subscription_tab'] ); - } - - return rest_ensure_response( - [ - 'config' => Reader_Activation::get_settings(), - 'prerequisites_status' => Reader_Activation::get_prerequisites_status(), - 'memberships' => self::get_memberships_settings(), - 'can_esp_sync' => Reader_Activation\ESP_Sync::can_esp_sync( true ), - ] - ); - } - - /** - * Activate reader activation and publish RAS prompts/segments. - * - * @param WP_REST_Request $request WP Rest Request object. - * @return WP_REST_Response - */ - public function api_activate_reader_activation( WP_REST_Request $request ) { - $skip_activation = $request->get_param( 'skip_activation' ) ?? false; - $response = $skip_activation ? true : Reader_Activation::activate(); - - if ( \is_wp_error( $response ) ) { - return new \WP_REST_Response( [ 'message' => $response->get_error_message() ], 400 ); - } - - if ( true === $response ) { - Reader_Activation::update_setting( 'enabled', true ); - } - - return rest_ensure_response( $response ); - } - /** * Update the Related Posts Max Age setting. * @@ -356,35 +226,6 @@ public function enqueue_scripts_and_styles() { true ); - $data = [ - 'has_memberships' => class_exists( 'WC_Memberships' ), - 'reader_activation_url' => \admin_url( 'admin.php?page=newspack-engagement-wizard#/reader-activation' ), - 'esp_metadata_fields' => Reader_Activation\Sync\Metadata::get_default_fields(), - ]; - - if ( method_exists( 'Newspack\Newsletters\Subscription_Lists', 'get_add_new_url' ) ) { - $data['new_subscription_lists_url'] = \Newspack\Newsletters\Subscription_Lists::get_add_new_url(); - } - - if ( method_exists( 'Newspack_Newsletters_Subscription', 'get_lists' ) ) { - $data['available_newsletter_lists'] = \Newspack_Newsletters_Subscription::get_lists(); - } - - $newspack_popups = Configuration_Managers::configuration_manager_class_for_plugin_slug( 'newspack-popups' ); - if ( $newspack_popups->is_configured() ) { - $data['preview_query_keys'] = $newspack_popups->preview_query_keys(); - $data['preview_post'] = $newspack_popups->preview_post(); - $data['preview_archive'] = $newspack_popups->preview_archive(); - } - - $data['is_skipped_campaign_setup'] = get_option( static::SKIP_CAMPAIGN_SETUP_OPTION, '' ); - - \wp_localize_script( - 'newspack-engagement-wizard', - 'newspack_engagement_wizard', - $data - ); - \wp_register_style( 'newspack-engagement-wizard', Newspack::plugin_url() . '/dist/engagement.css', @@ -411,15 +252,4 @@ public static function sanitize_categories( $categories ) { } return $sanitized; } - - /** - * Set the newsletters settings url - * - * @param string $url URL to the Newspack Newsletters settings page. - * - * @return string URL to the Newspack Newsletters settings page. - */ - public function newsletters_settings_url( $url = '' ) { - return admin_url( 'admin.php?page=newspack-engagement-wizard#/newsletters' ); - } } diff --git a/src/admin/style.scss b/src/admin/style.scss index c36357de9b..9a73bc1b59 100644 --- a/src/admin/style.scss +++ b/src/admin/style.scss @@ -130,6 +130,16 @@ h1 { /** * Wizards */ +// Only apply styles if there are sections and is immediate descendent. +.newspack-wizard__content:has(> .newspack-wizard__sections) { + margin: 0; + max-width: 100%; + padding: 0 0 32px; + + * { + box-sizing: border-box; + } +} .newspack-wizard-page { #screen-meta-links { position: absolute; diff --git a/src/wizards/engagement/components/active-campaign.js b/src/wizards/audience/components/active-campaign.js similarity index 100% rename from src/wizards/engagement/components/active-campaign.js rename to src/wizards/audience/components/active-campaign.js diff --git a/src/wizards/engagement/components/mailchimp.js b/src/wizards/audience/components/mailchimp.js similarity index 100% rename from src/wizards/engagement/components/mailchimp.js rename to src/wizards/audience/components/mailchimp.js diff --git a/src/wizards/engagement/components/metadata-fields.js b/src/wizards/audience/components/metadata-fields.js similarity index 100% rename from src/wizards/engagement/components/metadata-fields.js rename to src/wizards/audience/components/metadata-fields.js diff --git a/src/wizards/engagement/components/prerequisite.tsx b/src/wizards/audience/components/prerequisite.tsx similarity index 68% rename from src/wizards/engagement/components/prerequisite.tsx rename to src/wizards/audience/components/prerequisite.tsx index b11c25be77..9431f28ba8 100644 --- a/src/wizards/engagement/components/prerequisite.tsx +++ b/src/wizards/audience/components/prerequisite.tsx @@ -7,10 +7,8 @@ import { ExternalLink } from '@wordpress/components'; /** * Internal dependencies */ -import { PrequisiteProps } from './types'; import { ActionCard, Button, Grid, TextControl } from '../../../components/src'; import { HANDOFF_KEY } from '../../../components/src/consts'; -import type { Config, ConfigKey } from './types'; /** * Expandable ActionCard for RAS prerequisites checklist. @@ -62,15 +60,27 @@ export default function Prerequisite( {
{ fieldKeys.map( fieldName => { - if ( ! prerequisite.fields || ! prerequisite.fields[ fieldName ] ) { + if ( + ! prerequisite.fields || + ! prerequisite.fields[ fieldName ] + ) { return undefined; } return ( ); } ) } @@ -82,7 +92,8 @@ export default function Prerequisite( { fieldKeys.forEach( fieldName => { if ( config[ fieldName ] ) { // @ts-ignore - not sure what's the issue here. - dataToSave[ fieldName ] = config[ fieldName ]; + dataToSave[ fieldName ] = + config[ fieldName ]; } } ); saveConfig( dataToSave ); @@ -92,10 +103,21 @@ export default function Prerequisite( { { inFlight ? __( 'Saving…', 'newspack-plugin' ) : sprintf( - // Translators: Save or Update settings. - __( '%s settings', 'newspack-plugin' ), - isValid ? __( 'Update', 'newspack-plugin' ) : __( 'Save', 'newspack-plugin' ) - ) } + // Translators: Save or Update settings. + __( + '%s settings', + 'newspack-plugin' + ), + isValid + ? __( + 'Update', + 'newspack-plugin' + ) + : __( + 'Save', + 'newspack-plugin' + ) + ) }
@@ -106,7 +128,9 @@ export default function Prerequisite( { href && prerequisite.action_text && (
- { ( ! prerequisite.hasOwnProperty( 'action_enabled' ) || + { ( ! prerequisite.hasOwnProperty( + 'action_enabled' + ) || prerequisite.action_enabled ) && ( - ) } - { prerequisite.hasOwnProperty( 'action_enabled' ) && ! prerequisite.action_enabled && ( - ) } + { prerequisite.hasOwnProperty( 'action_enabled' ) && + ! prerequisite.action_enabled && ( + + ) }
) @@ -159,7 +195,9 @@ export default function Prerequisite( { let status = __( 'Pending', 'newspack-plugin' ); if ( isValid ) { status = `${ __( 'Ready', 'newspack-plugin' ) } ${ - prerequisite.is_skipped ? `(${ __( 'Skipped', 'newspack-plugin' ) })` : '' + prerequisite.is_skipped + ? `(${ __( 'Skipped', 'newspack-plugin' ) })` + : '' }`; } if ( prerequisite.is_unavailable ) { diff --git a/src/wizards/audience/components/prompt.tsx b/src/wizards/audience/components/prompt.tsx new file mode 100644 index 0000000000..7715b89320 --- /dev/null +++ b/src/wizards/audience/components/prompt.tsx @@ -0,0 +1,563 @@ +/* eslint-disable no-nested-ternary */ + +/** + * WordPress dependencies + */ +import { __, sprintf } from '@wordpress/i18n'; +import { + BaseControl, + CheckboxControl, + ExternalLink, + Path, + SVG, + TextareaControl, +} from '@wordpress/components'; +import apiFetch from '@wordpress/api-fetch'; +import { Fragment, useEffect, useState } from '@wordpress/element'; + +/** + * External dependencies + */ +import { stringify } from 'qs'; + +/** + * Internal dependencies + */ +import { + ActionCard, + Button, + Grid, + ImageUpload, + Notice, + TextControl, + WebPreview, + hooks, +} from '../../../components/src'; + +type Attachment = { + id?: number; + source_url?: string; + url: string; +}; + +// Note: Schema and types for the `prompt` prop is defined in Newspack Campaigns: https://github.com/Automattic/newspack-popups/blob/trunk/includes/schemas/class-prompts.php +export default function Prompt( { + inFlight, + prompt, + setInFlight, + setPrompts, +}: PromptProps ) { + const [ values, setValues ] = useState< + InputValues | Record< string, never > + >( {} ); + const [ error, setError ] = useState< false | { message: string } >( + false + ); + const [ isDirty, setIsDirty ] = useState< boolean >( false ); + const [ success, setSuccess ] = useState< false | string >( false ); + const [ image, setImage ] = useState< null | Attachment >( null ); + const [ isSavingFromPreview, setIsSavingFromPreview ] = useState( false ); + + useEffect( () => { + if ( Array.isArray( prompt?.user_input_fields ) ) { + const fields = { ...values }; + prompt.user_input_fields.forEach( ( field: InputField ) => { + fields[ field.name ] = field.value || field.default; + } ); + setValues( fields ); + } + + if ( prompt.featured_image_id ) { + setInFlight( true ); + apiFetch< Attachment >( { + path: `/wp/v2/media/${ prompt.featured_image_id }`, + } ) + .then( ( attachment: Attachment ) => { + if ( attachment?.source_url || attachment?.url ) { + setImage( { + url: attachment.source_url || attachment.url, + } ); + } + } ) + .catch( setError ) + .finally( () => { + setInFlight( false ); + } ); + } + }, [ prompt ] ); + + // Clear success message after a few seconds. + useEffect( () => { + setTimeout( () => setSuccess( false ), 5000 ); + }, [ success ] ); + + const previewIcon = ( + + + + ); + + const getPreviewUrl = ( { + options, + slug, + }: { + options: PromptOptions; + slug: string; + } ) => { + const { placement, trigger_type: triggerType } = options; + const previewQueryKeys = + window.newspackAudienceConfiguration.preview_query_keys; + const abbreviatedKeys = { preset: slug, values }; + const optionsKeys = Object.keys( + options + ) as Array< PromptOptionsBaseKey >; + optionsKeys.forEach( key => { + if ( previewQueryKeys.hasOwnProperty( key ) ) { + // @ts-ignore To be fixed in the future perhaps. + abbreviatedKeys[ previewQueryKeys[ key ] ] = options[ key ]; + } + } ); + + let previewURL = '/'; + if ( + 'archives' === placement && + window.newspackAudienceConfiguration?.preview_archive + ) { + previewURL = window.newspackAudienceConfiguration.preview_archive; + } else if ( + ( 'inline' === placement || 'scroll' === triggerType ) && + window && + window.newspackAudienceConfiguration?.preview_post + ) { + previewURL = window.newspackAudienceConfiguration?.preview_post; + } + + return `${ previewURL }?${ stringify( { ...abbreviatedKeys } ) }`; + }; + + const unblock = hooks.usePrompt( + isDirty, + __( 'You have unsaved changes. Discard changes?', 'newspack-plugin' ) + ); + + const savePrompt = ( slug: string, data: InputValues ) => { + return new Promise< void >( ( res, rej ) => { + if ( unblock ) { + unblock(); + } + setError( false ); + setSuccess( false ); + setInFlight( true ); + apiFetch< [ PromptType ] >( { + path: '/newspack-popups/v1/reader-activation/campaign', + method: 'post', + data: { + slug, + data, + }, + } ) + .then( ( fetchedPrompts: Array< PromptType > ) => { + setPrompts( fetchedPrompts ); + setSuccess( __( 'Prompt saved.', 'newspack-plugin' ) ); + setIsDirty( false ); + res(); + } ) + .catch( err => { + setError( err ); + rej( err ); + } ) + .finally( () => { + setInFlight( false ); + } ); + } ); + }; + + const helpInfo = prompt.help_info || null; + + return ( + + { + +
+ { prompt.user_input_fields.map( + ( field: InputField ) => ( + // @ts-ignore TS doesn't like Fragments when used in a map function in this way. + + { 'array' === field.type && + Array.isArray( field.options ) && ( + + { field.options.map( option => ( + + -1 + } + onChange={ ( + value: boolean + ) => { + const toUpdate = + { + ...values, + }; + if ( + ! value && + toUpdate[ + field + .name + // @ts-ignore To be fixed in the future perhaps. + ].indexOf( + option.id + ) > -1 + ) { + toUpdate[ + field.name + // @ts-ignore To be fixed in the future perhaps. + ].value = + toUpdate[ + field + .name + // @ts-ignore To be fixed in the future perhaps. + ].splice( + toUpdate[ + field + .name + // @ts-ignore To be fixed in the future perhaps. + ].indexOf( + option.id + ), + 1 + ); + } + if ( + value && + toUpdate[ + field + .name + // @ts-ignore To be fixed in the future perhaps. + ].indexOf( + option.id + ) === -1 + ) { + toUpdate[ + field + .name + // @ts-ignore To be fixed in the future perhaps. + ].push( + option.id + ); + } + setValues( + toUpdate + ); + setIsDirty( + true + ); + } } + /> + + ) ) } + + ) } + { 'string' === field.type && + field.max_length && + Array.isArray( values ) && + 150 < field.max_length && ( + { + if ( + value.length > + // @ts-ignore There's a check for max_length above. + field.max_length + ) { + return; + } + + const toUpdate = { + ...values, + }; + toUpdate[ field.name ] = + value; + setValues( toUpdate ); + setIsDirty( true ); + } } + placeholder={ + typeof field.default === + 'string' + ? field.default + : '' + } + rows={ 10 } + // @ts-ignore TS still does not see it as a string. + value={ + typeof values[ + field.name + ] === 'string' + ? values[ field.name ] + : '' + } + /> + ) } + { 'string' === field.type && + field.max_length && + 150 >= field.max_length && ( + { + if ( + value.length > + // @ts-ignore There's a check for max_length above. + field.max_length + ) { + return; + } + + const toUpdate = { + ...values, + }; + toUpdate[ field.name ] = + value; + setValues( toUpdate ); + setIsDirty( true ); + } } + placeholder={ field.default } + value={ + values[ field.name ] || '' + } + /> + ) } + { 'int' === field.type && + 'featured_image_id' === field.name && ( + + { + const toUpdate = { + ...values, + }; + toUpdate[ field.name ] = + attachment?.id || 0; + if ( + toUpdate[ + field.name + ] !== + values[ field.name ] + ) { + } + setValues( toUpdate ); + setIsDirty( true ); + if ( attachment?.url ) { + setImage( + attachment + ); + } else { + setImage( null ); + } + } } + /> + + ) } + + ) + ) } + { error && ( + + ) } + { success && ( + + ) } +
+ + void; + } ) => ( + + ) } + /> +
+
+ { helpInfo && ( +
+ { helpInfo.screenshot && ( + { + ) } + { helpInfo.description && ( +

+ { ' ' } + { helpInfo.url && ( + + { __( + 'Learn more', + 'newspack-plugin' + ) } + + ) } +

+ ) } + { helpInfo.recommendations && ( + <> +

+ { __( + 'We recommend', + 'newspack-plugin' + ) } +

+
    + { helpInfo.recommendations.map( + ( recommendation, index ) => ( +
  • + +
  • + ) + ) } +
+ + ) } +
+ ) } +
+ } +
+ ); +} diff --git a/src/wizards/engagement/components/types.ts b/src/wizards/audience/types/index.d.ts similarity index 79% rename from src/wizards/engagement/components/types.ts rename to src/wizards/audience/types/index.d.ts index dc577e72e3..0e2fa50cca 100644 --- a/src/wizards/engagement/components/types.ts +++ b/src/wizards/audience/types/index.d.ts @@ -2,7 +2,7 @@ * Types for the Prequisite component. */ -export type PromptOptionsBase = { +type PromptOptionsBase = { background_color: string; display_title: boolean; hide_border: boolean; @@ -26,27 +26,7 @@ export type PromptOptionsBase = { utm_suppression: string; }; -export type PromptOptionsBaseKey = keyof PromptOptionsBase; - -declare global { - interface Window { - // Localized data on engagement wizard script. - newspack_engagement_wizard: { - has_reader_activation: boolean; - has_memberships: boolean; - new_subscription_lists_url: string; - reader_activation_url: string; - preview_query_keys: { - [ K in PromptOptionsBaseKey ]: string; - }; - preview_post: string; - preview_archive: string; - }; - newspack_reader_revenue: { - can_use_name_your_price: boolean; - }; - } -} +type PromptOptionsBaseKey = keyof PromptOptionsBase; // Available transactional email slugs. type EmailSlugs = @@ -57,7 +37,7 @@ type EmailSlugs = | 'reader-activation-delete-account'; // RAS config inherited from RAS wizard view. -export type Config = { +type Config = { enabled?: boolean; enabled_account_link?: boolean; account_link_menu_locations?: [ 'tertiary-menu' ]; @@ -88,10 +68,10 @@ export type Config = { contact_email_address?: string; }; -export type ConfigKey = keyof Config; +type ConfigKey = keyof Config; // Props for the Prequisite component. -export type PrequisiteProps = { +type PrequisiteProps = { config: Config; getSharedProps: ( configKey: string, @@ -131,7 +111,7 @@ export type PrequisiteProps = { }; }; -export type InputField = { +type InputField = { name: string; type: string; label: string; @@ -147,7 +127,7 @@ export type InputField = { }; // Schema is defined in Newspack Campaigns: https://github.com/Automattic/newspack-popups/blob/trunk/includes/schemas/class-prompts.php -export type PromptType = { +type PromptType = { status: string; slug: string; title: string; @@ -157,7 +137,7 @@ export type PromptType = { { id: number; name: string; - } + }, ]; options: PromptOptions; user_input_fields: [ InputField ]; @@ -170,7 +150,7 @@ export type PromptType = { ready?: boolean; }; -export type PromptOptions = PromptOptionsBase & { +type PromptOptions = PromptOptionsBase & { post_types: Array< string >; archive_page_types: Array< string >; additional_classes: string; @@ -178,46 +158,45 @@ export type PromptOptions = PromptOptionsBase & { { id: number; name: string; - } + }, ]; excluded_tags: [ { id: number; name: string; - } + }, ]; categories: [ { id: number; name: string; - } + }, ]; tags: [ { id: number; name: string; - } + }, ]; campaign_groups: [ { id: number; name: string; - } + }, ]; }; -export type Attachment = { - id?: number; - source_url?: string; - url: string; -}; - -export type InputValues = { - [ fieldName: string ]: string | number | Array< string > | Array< number > | boolean; +type InputValues = { + [ fieldName: string ]: + | string + | number + | Array< string > + | Array< number > + | boolean; }; // Props for the Prompt component. -export type PromptProps = { +type PromptProps = { inFlight: boolean; setInFlight: ( inFlight: boolean ) => void; prompt: PromptType; diff --git a/src/wizards/audience/views/campaigns/index.js b/src/wizards/audience/views/campaigns/index.js new file mode 100644 index 0000000000..f59abe7bbe --- /dev/null +++ b/src/wizards/audience/views/campaigns/index.js @@ -0,0 +1,23 @@ +/** + * Configuration + */ + +/** + * WordPress dependencies. + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies. + */ +import { withWizard } from '../../../../components/src'; + +function AudienceCampaigns() { + return ( +

+ { __( 'Audience Campaigns - Coming Soon!', 'newspack-plugin' ) } +

+ ); +} + +export default withWizard( AudienceCampaigns ); diff --git a/src/wizards/engagement/views/reader-activation/campaign.js b/src/wizards/audience/views/configuration/campaign.js similarity index 71% rename from src/wizards/engagement/views/reader-activation/campaign.js rename to src/wizards/audience/views/configuration/campaign.js index 34ea029edb..d70d1f52b6 100644 --- a/src/wizards/engagement/views/reader-activation/campaign.js +++ b/src/wizards/audience/views/configuration/campaign.js @@ -1,4 +1,4 @@ -/* global newspack_engagement_wizard */ +/* global newspackAudienceConfiguration */ /** * WordPress dependencies @@ -10,10 +10,10 @@ import { useEffect, useState } from '@wordpress/element'; /** * Internal dependencies */ +import WizardsTab from '../../../wizards-tab'; import { Button, Notice, - SectionHeader, Waiting, withWizardScreen, utils, @@ -25,7 +25,8 @@ import './style.scss'; const { useHistory } = Router; export default withWizardScreen( () => { - const { is_skipped_campaign_setup, reader_activation_url } = newspack_engagement_wizard; + const { is_skipped_campaign_setup, reader_activation_url } = + newspackAudienceConfiguration; const [ inFlight, setInFlight ] = useState( false ); const [ error, setError ] = useState( false ); @@ -71,18 +72,21 @@ export default withWizardScreen( () => { setSkipped( { ...skipped, status: 'pending' } ); try { const request = await apiFetch( { - path: '/newspack/v1/wizard/newspack-engagement-wizard/reader-activation/skip-campaign-setup', + path: '/newspack/v1/wizard/newspack-audience-configuration/reader-activation/skip-campaign-setup', method: 'POST', data: { skip: ! skipped.isSkipped }, } ); if ( ! request.updated ) { - setError( { message: __( 'Server not updated', 'newspack-plugin' ) } ); + setError( { + message: __( 'Server not updated', 'newspack-plugin' ), + } ); setSkipped( { isSkipped: false, status: '' } ); return; } setSkipped( { isSkipped: Boolean( request.skipped ), status: '' } ); - newspack_engagement_wizard.is_skipped_campaign_setup = request.skipped ? '1' : ''; - history.push( '/reader-activation/complete' ); + newspackAudienceConfiguration.is_skipped_campaign_setup = + request.skipped ? '1' : ''; + history.push( '/complete' ); } catch ( err ) { setError( err ); setSkipped( { isSkipped: false, status: '' } ); @@ -101,17 +105,22 @@ export default withWizardScreen( () => { }, [ prompts ] ); return ( -
- + { error && ( ) } @@ -134,27 +143,37 @@ export default withWizardScreen( () => {
-
-
+ ); } ); diff --git a/src/wizards/engagement/views/reader-activation/complete.js b/src/wizards/audience/views/configuration/complete.js similarity index 74% rename from src/wizards/engagement/views/reader-activation/complete.js rename to src/wizards/audience/views/configuration/complete.js index e04124527b..a04a2746ab 100644 --- a/src/wizards/engagement/views/reader-activation/complete.js +++ b/src/wizards/audience/views/configuration/complete.js @@ -1,4 +1,4 @@ -/* global newspack_engagement_wizard */ +/* global newspackAudienceConfiguration */ /** * WordPress dependencies @@ -11,9 +11,9 @@ import { useEffect, useRef, useState } from '@wordpress/element'; /** * Internal dependencies */ +import WizardsTab from '../../../wizards-tab'; import { Button, - SectionHeader, withWizardScreen, Card, Notice, @@ -71,7 +71,7 @@ export default withWizardScreen( () => { const [ activationSteps, setActivationSteps ] = useState( Object.values( DEFAULT_ACTIVATION_STEPS ) ); - const { reader_activation_url, is_skipped_campaign_setup = '' } = newspack_engagement_wizard; + const { reader_activation_url, is_skipped_campaign_setup = '' } = newspackAudienceConfiguration; const isSkippedCampaignSetup = is_skipped_campaign_setup === '1'; useEffect( () => { @@ -125,7 +125,7 @@ export default withWizardScreen( () => { try { setCompleted( await apiFetch( { - path: '/newspack/v1/wizard/newspack-engagement-wizard/reader-activation/activate', + path: '/newspack/v1/wizard/newspack-audience-configuration/reader-activation/activate', method: 'post', data: { skip_activation: isSkippedCampaignSetup, @@ -139,9 +139,9 @@ export default withWizardScreen( () => { return (
- ( + description={ <> { __( 'An easy way to let your readers register for your site, sign up for newsletters, or become donors and paid members. ', @@ -153,46 +153,48 @@ export default withWizardScreen( () => { { __( 'Learn more', 'newspack-plugin' ) } - ) } - /> - { inFlight && ( - - - - ) } - { ! inFlight && ( - -

{ __( "You're all set to enable Reader Activation!", 'newspack-plugin' ) }

-

{ __( 'This is what will happen next:', 'newspack-plugin' ) }

- - - - - - { error && ( - + + { inFlight && ( + + - ) } + + ) } + { ! inFlight && ( + +

{ __( "You're all set to enable Reader Activation!", 'newspack-plugin' ) }

+

{ __( 'This is what will happen next:', 'newspack-plugin' ) }

+ + + + + + { error && ( + + ) } - - + + + -
- ) } -
- -
+ ) } +
+ +
+
); } ); diff --git a/src/wizards/audience/views/configuration/index.js b/src/wizards/audience/views/configuration/index.js new file mode 100644 index 0000000000..356717b50a --- /dev/null +++ b/src/wizards/audience/views/configuration/index.js @@ -0,0 +1,72 @@ +/** + * Configuration + */ + +/** + * WordPress dependencies. + */ +import { __ } from '@wordpress/i18n'; +import { Component, Fragment } from '@wordpress/element'; + +/** + * Internal dependencies. + */ +import Main from './settings'; +import Campaign from './campaign'; +import Complete from './complete'; +import { withWizard } from '../../../../components/src'; +import Router from '../../../../components/src/proxied-imports/router'; + +const { HashRouter, Redirect, Route, Switch } = Router; + +class AudienceConfiguration extends Component { + /** + * Render + */ + render() { + const { pluginRequirements, wizardApiFetch } = this.props; + + const props = { + headerText: __( + 'Audience Development / Configuration', + 'newspack-plugin' + ), + tabbedNavigation: [], + wizardApiFetch, + }; + return ( + + + + { pluginRequirements } +
} + /> + ( + + ) } + /> + } + /> + + + + + ); + } +} + +export default withWizard( AudienceConfiguration ); + \ No newline at end of file diff --git a/src/wizards/engagement/views/reader-activation/index.js b/src/wizards/audience/views/configuration/settings.js similarity index 61% rename from src/wizards/engagement/views/reader-activation/index.js rename to src/wizards/audience/views/configuration/settings.js index e7a68efad9..9fe6f4f30e 100644 --- a/src/wizards/engagement/views/reader-activation/index.js +++ b/src/wizards/audience/views/configuration/settings.js @@ -1,4 +1,4 @@ -/* global newspack_engagement_wizard */ +/* global newspackAudienceConfiguration */ /** * WordPress dependencies */ @@ -21,6 +21,7 @@ import { Waiting, withWizardScreen, } from '../../../../components/src'; +import WizardsTab from '../../../wizards-tab'; import Prerequisite from '../../components/prerequisite'; import ActiveCampaign from '../../components/active-campaign'; import MetadataFields from '../../components/metadata-fields'; @@ -47,7 +48,7 @@ export default withWizardScreen( ( { wizardApiFetch } ) => { setError( false ); setInFlight( true ); apiFetch( { - path: '/newspack/v1/wizard/newspack-engagement-wizard/reader-activation', + path: '/newspack/v1/wizard/newspack-audience-configuration/reader-activation', } ) .then( ( { config: fetchedConfig, prerequisites_status, memberships, can_esp_sync } ) => { setPrerequisites( prerequisites_status ); @@ -62,7 +63,7 @@ export default withWizardScreen( ( { wizardApiFetch } ) => { setError( false ); setInFlight( true ); wizardApiFetch( { - path: '/newspack/v1/wizard/newspack-engagement-wizard/reader-activation', + path: '/newspack/v1/wizard/newspack-audience-configuration/reader-activation', method: 'post', quiet: true, data, @@ -85,7 +86,7 @@ export default withWizardScreen( ( { wizardApiFetch } ) => { }, [] ); useEffect( () => { apiFetch( { - path: '/newspack/v1/wizard/newspack-engagement-wizard/newsletters', + path: '/newspack/v1/wizard/newspack-newsletters/settings', } ).then( data => { setIsMailchimp( data?.settings?.newspack_newsletters_service_provider?.value === 'mailchimp' @@ -160,30 +161,39 @@ export default withWizardScreen( ( { wizardApiFetch } ) => { }; return ( - <> - ( - <> - { __( - 'Newspack’s Reader Activation system is a set of features that aim to increase reader loyalty, promote engagement, and drive revenue. ', - 'newspack-plugin' - ) } - - { __( 'Learn more', 'newspack-plugin' ) } - - - ) } - /> + + { __( + "Newspack's Reader Activation system is a set of features that aim to increase reader loyalty, promote engagement, and drive revenue. ", + 'newspack-plugin' + ) } + + { __( 'Learn more', 'newspack-plugin' ) } + + + } + > { error && ( ) } { 0 < missingPlugins.length && ( ) } @@ -197,7 +207,13 @@ export default withWizardScreen( ( { wizardApiFetch } ) => { /> ) } { prerequisites && allReady && config.enabled && ( - + ) } { ! prerequisites && ( <> @@ -228,56 +244,88 @@ export default withWizardScreen( ( { wizardApiFetch } ) => { { config.enabled && ( <>
- ) } { showAdvanced && ( - { newspack_engagement_wizard.has_memberships && membershipsConfig ? ( + { newspackAudienceConfiguration.has_memberships && + membershipsConfig ? ( <> - { membershipsConfig?.plans && 1 < membershipsConfig.plans.length && ( - - setMembershipsConfig( { ...membershipsConfig, require_all_plans: value } ) - } - toggleChecked={ membershipsConfig.require_all_plans } - /> - ) } + { membershipsConfig?.plans && + 1 < membershipsConfig.plans.length && ( + + setMembershipsConfig( { + ...membershipsConfig, + require_all_plans: value, + } ) + } + toggleChecked={ + membershipsConfig.require_all_plans + } + /> + ) } - setMembershipsConfig( { ...membershipsConfig, show_on_subscription_tab: value } ) + setMembershipsConfig( { + ...membershipsConfig, + show_on_subscription_tab: value, + } ) + } + toggleChecked={ + membershipsConfig.show_on_subscription_tab } - toggleChecked={ membershipsConfig.show_on_subscription_tab } />
@@ -286,7 +334,10 @@ export default withWizardScreen( ( { wizardApiFetch } ) => { { emails?.length > 0 && ( <> { titleLink={ email.edit_link } href={ email.edit_link } description={ email.description } - actionText={ __( 'Edit', 'newspack-plugin' ) } + actionText={ __( + 'Edit', + 'newspack-plugin' + ) } isSmall /> ) ) } @@ -307,35 +361,55 @@ export default withWizardScreen( ( { wizardApiFetch } ) => { ) } - + updateConfig( 'use_custom_lists', value ) } + toggleOnChange={ value => + updateConfig( 'use_custom_lists', value ) + } /> { config.use_custom_lists && ( updateConfig( 'newsletter_lists', selected ) } + onChange={ selected => + updateConfig( 'newsletter_lists', selected ) + } /> ) }
{ ) } hasGreyHeader={ true } isMedium - title={ __( 'Sync contacts to ESP', 'newspack-plugin' ) } + title={ __( + 'Sync contacts to ESP', + 'newspack-plugin' + ) } toggleChecked={ config.sync_esp } - toggleOnChange={ value => updateConfig( 'sync_esp', value ) } + toggleOnChange={ value => + updateConfig( 'sync_esp', value ) + } > { config.sync_esp && ( <> - { 0 < Object.keys(espSyncErrors).length && ( + { 0 < Object.keys( espSyncErrors ).length && ( ) } { isMailchimp && ( { if ( key === 'audienceId' ) { - updateConfig( 'mailchimp_audience_id', value ); + updateConfig( + 'mailchimp_audience_id', + value + ); } - if ( key === 'readerDefaultStatus' ) { - updateConfig( 'mailchimp_reader_default_status', value ); + if ( + key === 'readerDefaultStatus' + ) { + updateConfig( + 'mailchimp_reader_default_status', + value + ); } } } /> ) } { isActiveCampaign && ( { if ( key === 'masterList' ) { - updateConfig( 'active_campaign_master_list', value ); + updateConfig( + 'active_campaign_master_list', + value + ); } } } /> ) } { ) }
+ {/* TODO: Add Platform from `/wp-admin/admin.php?page=newspack-reader-revenue-wizard#/`*/} + {/* TODO: Add Stripe Setup from `/wp-admin/admin.php?page=newspack-reader-revenue-wizard#/stripe-setup`*/} + {/* TODO: Add Saleforce Settings from `/wp-admin/admin.php?page=newspack-reader-revenue-wizard#/salesforce`*/}
) } - +
); } ); diff --git a/src/wizards/engagement/views/reader-activation/style.scss b/src/wizards/audience/views/configuration/style.scss similarity index 100% rename from src/wizards/engagement/views/reader-activation/style.scss rename to src/wizards/audience/views/configuration/style.scss diff --git a/src/wizards/engagement/components/prompt.tsx b/src/wizards/engagement/components/prompt.tsx deleted file mode 100644 index f51a2e27b7..0000000000 --- a/src/wizards/engagement/components/prompt.tsx +++ /dev/null @@ -1,373 +0,0 @@ -/* eslint-disable no-nested-ternary */ - -/** - * WordPress dependencies - */ -import { __, sprintf } from '@wordpress/i18n'; -import { - BaseControl, - CheckboxControl, - ExternalLink, - Path, - SVG, - TextareaControl, -} from '@wordpress/components'; -import apiFetch from '@wordpress/api-fetch'; -import { Fragment, useEffect, useState } from '@wordpress/element'; - -/** - * External dependencies - */ -import { stringify } from 'qs'; - -/** - * Internal dependencies - */ -import { - Attachment, - InputField, - InputValues, - PromptOptions, - PromptProps, - PromptType, - PromptOptionsBaseKey, -} from './types'; -import { - ActionCard, - Button, - Grid, - ImageUpload, - Notice, - TextControl, - WebPreview, - hooks, -} from '../../../components/src'; - -// Note: Schema and types for the `prompt` prop is defined in Newspack Campaigns: https://github.com/Automattic/newspack-popups/blob/trunk/includes/schemas/class-prompts.php -export default function Prompt( { inFlight, prompt, setInFlight, setPrompts }: PromptProps ) { - const [ values, setValues ] = useState< InputValues | Record< string, never > >( {} ); - const [ error, setError ] = useState< false | { message: string } >( false ); - const [ isDirty, setIsDirty ] = useState< boolean >( false ); - const [ success, setSuccess ] = useState< false | string >( false ); - const [ image, setImage ] = useState< null | Attachment >( null ); - const [ isSavingFromPreview, setIsSavingFromPreview ] = useState( false ); - - useEffect( () => { - if ( Array.isArray( prompt?.user_input_fields ) ) { - const fields = { ...values }; - prompt.user_input_fields.forEach( ( field: InputField ) => { - fields[ field.name ] = field.value || field.default; - } ); - setValues( fields ); - } - - if ( prompt.featured_image_id ) { - setInFlight( true ); - apiFetch< Attachment >( { - path: `/wp/v2/media/${ prompt.featured_image_id }`, - } ) - .then( ( attachment: Attachment ) => { - if ( attachment?.source_url || attachment?.url ) { - setImage( { url: attachment.source_url || attachment.url } ); - } - } ) - .catch( setError ) - .finally( () => { - setInFlight( false ); - } ); - } - }, [ prompt ] ); - - // Clear success message after a few seconds. - useEffect( () => { - setTimeout( () => setSuccess( false ), 5000 ); - }, [ success ] ); - - const previewIcon = ( - - - - ); - - const getPreviewUrl = ( { options, slug }: { options: PromptOptions; slug: string } ) => { - const { placement, trigger_type: triggerType } = options; - const previewQueryKeys = window.newspack_engagement_wizard.preview_query_keys; - const abbreviatedKeys = { preset: slug, values }; - const optionsKeys = Object.keys( options ) as Array< PromptOptionsBaseKey >; - optionsKeys.forEach( key => { - if ( previewQueryKeys.hasOwnProperty( key ) ) { - // @ts-ignore To be fixed in the future perhaps. - abbreviatedKeys[ previewQueryKeys[ key ] ] = options[ key ]; - } - } ); - - let previewURL = '/'; - if ( 'archives' === placement && window.newspack_engagement_wizard?.preview_archive ) { - previewURL = window.newspack_engagement_wizard.preview_archive; - } else if ( - ( 'inline' === placement || 'scroll' === triggerType ) && - window && - window.newspack_engagement_wizard?.preview_post - ) { - previewURL = window.newspack_engagement_wizard?.preview_post; - } - - return `${ previewURL }?${ stringify( { ...abbreviatedKeys } ) }`; - }; - - const unblock = hooks.usePrompt( - isDirty, - __( 'You have unsaved changes. Discard changes?', 'newspack-plugin' ) - ); - - const savePrompt = ( slug: string, data: InputValues ) => { - return new Promise< void >( ( res, rej ) => { - if ( unblock ) { - unblock(); - } - setError( false ); - setSuccess( false ); - setInFlight( true ); - apiFetch< [ PromptType ] >( { - path: '/newspack-popups/v1/reader-activation/campaign', - method: 'post', - data: { - slug, - data, - }, - } ) - .then( ( fetchedPrompts: Array< PromptType > ) => { - setPrompts( fetchedPrompts ); - setSuccess( __( 'Prompt saved.', 'newspack-plugin' ) ); - setIsDirty( false ); - res(); - } ) - .catch( err => { - setError( err ); - rej( err ); - } ) - .finally( () => { - setInFlight( false ); - } ); - } ); - }; - - const helpInfo = prompt.help_info || null; - - return ( - - { - -
- { prompt.user_input_fields.map( ( field: InputField ) => ( - // @ts-ignore TS doesn't like Fragments when used in a map function in this way. - - { 'array' === field.type && Array.isArray( field.options ) && ( - - { field.options.map( option => ( - - -1 } - onChange={ ( value: boolean ) => { - const toUpdate = { ...values }; - // @ts-ignore To be fixed in the future perhaps. - if ( ! value && toUpdate[ field.name ].indexOf( option.id ) > -1 ) { - // @ts-ignore To be fixed in the future perhaps. - toUpdate[ field.name ].value = toUpdate[ field.name ].splice( - // @ts-ignore To be fixed in the future perhaps. - toUpdate[ field.name ].indexOf( option.id ), - 1 - ); - } - // @ts-ignore To be fixed in the future perhaps. - if ( value && toUpdate[ field.name ].indexOf( option.id ) === -1 ) { - // @ts-ignore To be fixed in the future perhaps. - toUpdate[ field.name ].push( option.id ); - } - setValues( toUpdate ); - setIsDirty( true ); - } } - /> - - ) ) } - - ) } - { 'string' === field.type && field.max_length && 150 < field.max_length && ( - { - // @ts-ignore There's a check for max_length above. - if ( value.length > field.max_length ) { - return; - } - - const toUpdate = { ...values }; - toUpdate[ field.name ] = value; - setValues( toUpdate ); - setIsDirty( true ); - } } - placeholder={ typeof field.default === 'string' ? field.default : '' } - rows={ 10 } - // @ts-ignore TS still does not see it as a string. - value={ typeof values[ field.name ] === 'string' ? values[ field.name ] : '' } - /> - ) } - { 'string' === field.type && field.max_length && 150 >= field.max_length && ( - { - // @ts-ignore There's a check for max_length above. - if ( value.length > field.max_length ) { - return; - } - - const toUpdate = { ...values }; - toUpdate[ field.name ] = value; - setValues( toUpdate ); - setIsDirty( true ); - } } - placeholder={ field.default } - value={ values[ field.name ] || '' } - /> - ) } - { 'int' === field.type && 'featured_image_id' === field.name && ( - - { - const toUpdate = { ...values }; - toUpdate[ field.name ] = attachment?.id || 0; - if ( toUpdate[ field.name ] !== values[ field.name ] ) { - } - setValues( toUpdate ); - setIsDirty( true ); - if ( attachment?.url ) { - setImage( attachment ); - } else { - setImage( null ); - } - } } - /> - - ) } - - ) ) } - { error && ( - - ) } - { success && } -
- - void } ) => ( - - ) } - /> -
-
- { helpInfo && ( -
- { helpInfo.screenshot && { } - { helpInfo.description && ( -

- { ' ' } - { helpInfo.url && ( - - { __( 'Learn more', 'newspack-plugin' ) } - - ) } -

- ) } - { helpInfo.recommendations && ( - <> -

- { __( 'We recommend', 'newspack-plugin' ) } -

-
    - { helpInfo.recommendations.map( ( recommendation, index ) => ( -
  • - -
  • - ) ) } -
- - ) } -
- ) } -
- } -
- ); -} diff --git a/src/wizards/engagement/index.js b/src/wizards/engagement/index.js index a640b19148..f307a94e1e 100644 --- a/src/wizards/engagement/index.js +++ b/src/wizards/engagement/index.js @@ -15,13 +15,7 @@ import { __ } from '@wordpress/i18n'; */ import { withWizard } from '../../components/src'; import Router from '../../components/src/proxied-imports/router'; -import { - ReaderActivation, - ReaderActivationCampaign, - ReaderActivationComplete, - Social, - RelatedContent, -} from './views'; +import { Social, RelatedContent } from './views'; const { HashRouter, Redirect, Route, Switch } = Router; @@ -79,14 +73,8 @@ class EngagementWizard extends Component { const { relatedPostsEnabled, relatedPostsError, relatedPostsMaxAge, relatedPostsUpdated } = this.state; - const defaultPath = '/reader-activation'; + const defaultPath = '/social'; const tabbed_navigation = [ - { - label: __( 'Reader Activation', 'newspack-plugin' ), - path: '/reader-activation', - exact: true, - activeTabPaths: ['/reader-activation/*'], - }, { label: __( 'Social', 'newspack-plugin' ), path: '/social', @@ -107,43 +95,6 @@ class EngagementWizard extends Component { { pluginRequirements } - ( - - ) } - /> - ( - - ) } - /> - ( - - ) } - /> = { /** * `page` param with `newspack-*`. @@ -48,6 +46,24 @@ const components: Record< string, any > = { ) ), }, + 'newspack-audience-configuration': { + label: __( 'Audience Configuration', 'newspack-plugin' ), + component: lazy( + () => + import( + /* webpackChunkName: "audience-wizards" */ './audience/views/configuration' + ) + ), + }, + 'newspack-audience-campaigns': { + label: __( 'Audience Campaigns', 'newspack-plugin' ), + component: lazy( + () => + import( + /* webpackChunkName: "audience-wizards" */ './audience/views/campaigns' + ) + ), + }, } as const; const AdminPageLoader = ( { label }: { label: string } ) => { @@ -82,7 +98,7 @@ const AdminPages = () => { ); }; -if ( rootElement && ALLOWED_PAGES.includes( pageParam ) ) { +if ( rootElement && pageParam in components ) { render( , rootElement ); } else { // eslint-disable-next-line no-console diff --git a/src/wizards/newspack/views/dashboard/style.scss b/src/wizards/newspack/views/dashboard/style.scss index 6970389f0f..990867f699 100644 --- a/src/wizards/newspack/views/dashboard/style.scss +++ b/src/wizards/newspack/views/dashboard/style.scss @@ -9,16 +9,6 @@ background-color: colors.$primary-600; } -.newspack-wizard__content { - margin: 0; - max-width: 100%; - padding: 0 0 32px; - - * { - box-sizing: border-box; - } -} - .newspack-dashboard__brand-header { padding: 40px 0; diff --git a/src/wizards/newspack/views/settings/connections/index.tsx b/src/wizards/newspack/views/settings/connections/index.tsx index 0af37e0bf7..74cfc86dba 100644 --- a/src/wizards/newspack/views/settings/connections/index.tsx +++ b/src/wizards/newspack/views/settings/connections/index.tsx @@ -34,12 +34,17 @@ function Connections() { { /* APIs; google */ } - { connections.sections.apis.dependencies?.googleOAuth && } + { connections.sections.apis.dependencies?.googleOAuth && ( + + ) } { /* reCAPTCHA */ } - + @@ -55,7 +60,10 @@ function Connections() { { /* Custom Events */ } ; title: string; }; + newspackAudienceConfiguration: { + has_reader_activation: boolean; + has_memberships: boolean; + new_subscription_lists_url: string; + reader_activation_url: string; + preview_query_keys: { + [ K in PromptOptionsBaseKey ]: string; + }; + preview_post: string; + preview_archive: string; + }; + newspack_reader_revenue: { + can_use_name_your_price: boolean; + }; } } diff --git a/src/wizards/wizards-section.tsx b/src/wizards/wizards-section.tsx index d1409837a5..04234854ee 100644 --- a/src/wizards/wizards-section.tsx +++ b/src/wizards/wizards-section.tsx @@ -10,10 +10,11 @@ import { SectionHeader } from '../components/src'; /** * Section component. * - * @param props Component props. - * @param props.title Section title. - * @param props.description Section description. - * @param props.children Section children. + * @param props Component props. + * @param props.title Section title. + * @param props.description Section description. + * @param props.children Section children. + * @param props.scrollToAnchor Scroll to anchor. * * @return Component. */ @@ -21,15 +22,18 @@ export default function WizardSection( { title, description, children = null, + scrollToAnchor = null, }: { title?: string; description?: string; children: React.ReactNode; + scrollToAnchor?: string | null; } ) { return (
{ title && (

{ title }

+ { description &&

{ description }

} { children }
);