diff --git a/includes/Admin.php b/includes/Admin.php index 46e4aa21..a9e59f2a 100755 --- a/includes/Admin.php +++ b/includes/Admin.php @@ -26,6 +26,7 @@ class Admin { const TC_REMOVED_KEY = 'tiob_tc_removed'; const TC_NEW_NOTICE_DISMISSED = 'tiob_new_tc_notice_dismissed'; + const ONBOARDING_PROMO_NOTICE_DISMISSED = 'tiob_onboarding_promo_notice_dismissed'; const VISITED_LIBRARY_OPT = 'tiob_library_visited'; /** @@ -88,6 +89,7 @@ public function init() { add_action( 'wp_ajax_tpc_get_logs', array( $this, 'external_get_logs' ) ); add_action( 'wp_ajax_dismiss_new_tc_notice', array( $this, 'dismiss_new_tc_notice' ) ); + add_action( 'wp_ajax_dismiss_onboarding_promo_notice', array( $this, 'dismiss_onboarding_promo_notice' ) ); $this->register_feedback_settings(); @@ -161,6 +163,86 @@ public function dismiss_new_tc_notice() { $this->ensure_ajax_response( $response ); } + /** + * Dismiss onboarding promo notice. + * + * @return void + */ + public function dismiss_onboarding_promo_notice() { + $response = array( + 'success' => false, + 'code' => 'ti__ob_not_allowed', + 'message' => 'Not allowed!', + ); + + if ( ! isset( $_REQUEST['nonce'] ) ) { + $this->ensure_ajax_response( $response ); + return; + } + + $nonce = sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) ); + + if ( ! wp_verify_nonce( $nonce, 'dismiss_onboarding_promo_notice' ) ) { + $this->ensure_ajax_response( $response ); + return; + } + + if ( ! current_user_can( 'install_plugins' ) ) { + $this->ensure_ajax_response( $response ); + return; + } + + $response['success'] = true; + unset( $response['code'] ); + unset( $response['message'] ); + + update_option( self::ONBOARDING_PROMO_NOTICE_DISMISSED, 'yes' ); + $this->ensure_ajax_response( $response ); + } + + /** + * Decide if the onboarding promo notice should be shown. + * + * @return bool + */ + private function should_show_onboarding_promo_notice() { + return get_option( self::ONBOARDING_PROMO_NOTICE_DISMISSED, 'no' ) !== 'yes'; + } + + /** + * Decide if the business/agency variant of the onboarding promo text should be shown. + * + * @return bool + */ + private function should_show_business_agency_promo_text() { + $license_data = License::get_license_data(); + $license_key = isset( $license_data->key ) ? strtolower( trim( (string) $license_data->key ) ) : ''; + $license_tier = License::get_license_tier( 0 ); + $raw_tier = isset( $license_data->tier ) ? absint( $license_data->tier ) : 0; + $neve_plan = apply_filters( 'product_neve_license_plan', -1 ); + + if ( $license_key === '' || $license_key === 'free' ) { + return false; + } + + if ( ! License::has_active_license() || ! $this->has_valid_addons() ) { + return false; + } + + // Check Neve plan only if it's a valid category (not -1 default) + if ( -1 !== $neve_plan && in_array( $neve_plan, array( 1, 2, 3, 4, 5, 6, 7, 8, 9 ), true ) ) { + // Normalize Neve plan category to TPC tier using License::NEVE_CATEGORY_MAPPING + $normalized_neve_tier = isset( License::NEVE_CATEGORY_MAPPING[ $neve_plan ] ) ? License::NEVE_CATEGORY_MAPPING[ $neve_plan ] : -1; + return in_array( $normalized_neve_tier, array( 2, 3 ), true ); + } + + if ( in_array( $raw_tier, array( 1, 2, 7, 12, 18 ), true ) ) { + return false; + } + + return in_array( $license_tier, array( 2, 3 ), true ); + } + /** * Register hooks to prevent meta cloning for the templates. @@ -789,6 +871,7 @@ private function get_localization() { ), 'cleanupAllowed' => ( ! empty( get_transient( Active_State::STATE_NAME ) ) ) ? 'yes' : 'no', 'onboarding' => array(), + 'adminUrl' => admin_url(), 'hasFileSystem' => WP_Filesystem(), 'themesURL' => admin_url( 'themes.php' ), 'themeAction' => $this->get_theme_action(), @@ -804,6 +887,7 @@ private function get_localization() { 'upsellNotifications' => $this->get_upsell_notifications(), 'isValidLicense' => $this->has_valid_addons(), 'licenseTIOB' => License::get_license_data(), + 'onboardingShowProNoticeText' => $this->should_show_business_agency_promo_text(), 'emailSubscribe' => array( 'ajaxURL' => esc_url( admin_url( 'admin-ajax.php' ) ), 'nonce' => wp_create_nonce( 'skip_subscribe_nonce' ), @@ -855,6 +939,11 @@ private function get_localization() { 'ajaxURL' => esc_url( admin_url( 'admin-ajax.php' ) ), 'nonce' => wp_create_nonce( 'dismiss_new_tc_notice' ), ), + 'onboardingPromoNotice' => array( + 'show' => $this->should_show_onboarding_promo_notice(), + 'ajaxURL' => esc_url( admin_url( 'admin-ajax.php' ) ), + 'nonce' => wp_create_nonce( 'dismiss_onboarding_promo_notice' ), + ), 'onboardingPluginCompatibility' => array( 'hyve-lite' => is_php_version_compatible( '8.1' ), ), diff --git a/onboarding/src/Components/Header.js b/onboarding/src/Components/Header.js index 3c460bcc..cf0dff04 100644 --- a/onboarding/src/Components/Header.js +++ b/onboarding/src/Components/Header.js @@ -17,7 +17,10 @@ const Header = ( { handleLogoClick, importing, step, trackingId } ) => { step_id: step, step_status: 'exit', }; - const site = tiobDash.onboarding.homeUrl || ''; + const adminUrl = + tiobDash.onboarding?.adminUrl || + tiobDash.adminUrl || + ( tiobDash.onboarding.homeUrl || '' ) + '/wp-admin/'; const trackingPromise = track( trackingId, data ); @@ -36,7 +39,7 @@ const Header = ( { handleLogoClick, importing, step, trackingId } ) => { console.error( error ); } ) .finally( () => { - window.location.href = site + '/wp-admin'; + window.location.href = adminUrl; } ); }; diff --git a/onboarding/src/Components/OnboardingPromoNotice.js b/onboarding/src/Components/OnboardingPromoNotice.js new file mode 100644 index 00000000..9ddfee31 --- /dev/null +++ b/onboarding/src/Components/OnboardingPromoNotice.js @@ -0,0 +1,96 @@ +/* global tiobDash */ +import { __, sprintf } from '@wordpress/i18n'; +import { createInterpolateElement, useState } from '@wordpress/element'; +import { ajaxAction } from '../utils/rest'; + +const OnboardingPromoNotice = () => { + const shouldShowNotice = Boolean( tiobDash.onboardingPromoNotice?.show ); + const showProMessage = Boolean( tiobDash.onboardingShowProNoticeText ); + + const emailBody = sprintf( + /* translators: %s: double line break in the starter site request email template */ + __( + 'Hi Neve team,%1$sI\'m looking for a starter site for the following project:%1$sProject type: (e.g. Restaurant, Law Firm, SaaS)%1$sKey pages needed: (e.g. Home, About, Services, Contact)%1$sStyle preference: (e.g. Minimal, Bold, Corporate)%1$sAny references: (optional)%1$sThanks', + 'templates-patterns-collection' + ), + '\n\n' + ); + + const requestSiteLink = + 'mailto:contact@themeisle.com?subject=' + + encodeURIComponent( + __( 'Starter Site Request', 'templates-patterns-collection' ) + ) + + '&body=' + + encodeURIComponent( emailBody ); + + const noticeMessage = showProMessage + ? createInterpolateElement( + __( + 'Fresh designs built for every niche. Can\'t find what you\'re looking for? As a Pro user, request a site and we\'ll build it for you.', + 'templates-patterns-collection' + ), + { + requestSiteLink: ( + /* eslint-disable-next-line jsx-a11y/anchor-has-content */ + + ), + } + ) + : __( + 'From free to pro, fresh designs built for every niche. More coming soon.', + 'templates-patterns-collection' + ); + + const [ isVisible, setIsVisible ] = useState( shouldShowNotice ); + + if ( ! isVisible ) { + return null; + } + + const dismissNotice = () => { + setIsVisible( false ); + ajaxAction( + tiobDash.onboardingPromoNotice.ajaxURL, + 'dismiss_onboarding_promo_notice', + tiobDash.onboardingPromoNotice.nonce + ).catch( () => null ); + }; + + return ( +
+
+ { __( 'New', 'templates-patterns-collection' ) } +
+
+

+ { sprintf( + /* translators: %s: number of new starter sites */ + __( + '%s new starter sites, just landed.', + 'templates-patterns-collection' + ), + '80+' + ) } +

+

{ noticeMessage }

+
+ +
+ ); +}; + +export default OnboardingPromoNotice; diff --git a/onboarding/src/Components/Steps/SiteList.js b/onboarding/src/Components/Steps/SiteList.js index 7673a89e..10764cb3 100644 --- a/onboarding/src/Components/Steps/SiteList.js +++ b/onboarding/src/Components/Steps/SiteList.js @@ -6,6 +6,7 @@ import Toast from '../Toast'; import Filters from '../Filters'; import Sites from '../Sites'; import EditorSelector from '../EditorSelector'; +import OnboardingPromoNotice from '../OnboardingPromoNotice'; import SVG from '../../utils/svg'; const SiteList = ( { showToast, setShowToast, setFetching } ) => { @@ -53,6 +54,7 @@ const SiteList = ( { showToast, setShowToast, setFetching } ) => { + { ! tiobDash.isValidLicense && (