From e43c658f6986e1a6299e722f2c47bc071d2e5a45 Mon Sep 17 00:00:00 2001 From: Ian Ramos <5714212+IanRamosC@users.noreply.github.com> Date: Fri, 6 Dec 2024 00:33:06 -0300 Subject: [PATCH 1/8] My Jetpack: add feature as possible module recommendation --- .../components/product-cards-section/all.ts | 3 + .../brute-force-card.tsx | 20 +++ .../my-jetpack/_inc/data/constants.ts | 1 + projects/packages/my-jetpack/global.d.ts | 1 + .../src/products/class-brute-force.php | 153 ++++++++++++++++++ 5 files changed, 178 insertions(+) create mode 100644 projects/packages/my-jetpack/_inc/components/product-cards-section/brute-force-card.tsx create mode 100644 projects/packages/my-jetpack/src/products/class-brute-force.php diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/all.ts b/projects/packages/my-jetpack/_inc/components/product-cards-section/all.ts index 18169a3e57272..a2357f58c9cd0 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/all.ts +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/all.ts @@ -2,6 +2,7 @@ import AiCard from './ai-card'; import AntiSpamCard from './anti-spam-card'; import BackupCard from './backup-card'; import BoostCard from './boost-card'; +import BruteForceCard from './brute-force-card'; import CompleteCard from './complete-card'; import CrmCard from './crm-card'; import GrowthCard from './growth-card'; @@ -29,6 +30,8 @@ export const JetpackModuleToProductCard: { security: SecurityCard, growth: GrowthCard, complete: CompleteCard, + // Features: + 'feature_brute-force': BruteForceCard, // Not existing: extras: null, scan: null, diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/brute-force-card.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/brute-force-card.tsx new file mode 100644 index 0000000000000..f668f9df8c836 --- /dev/null +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/brute-force-card.tsx @@ -0,0 +1,20 @@ +import { PRODUCT_SLUGS } from '../../data/constants'; +import ProductCard from '../connected-product-card'; +import type { FC } from 'react'; + +interface BruteForceCardProps { + admin?: boolean; + recommendation?: boolean; +} + +const CompleteCard: FC< BruteForceCardProps > = ( { admin, recommendation } ) => { + return ( + + ); +}; + +export default CompleteCard; diff --git a/projects/packages/my-jetpack/_inc/data/constants.ts b/projects/packages/my-jetpack/_inc/data/constants.ts index c5dd1a84a63f7..ffe2ce63a9b38 100644 --- a/projects/packages/my-jetpack/_inc/data/constants.ts +++ b/projects/packages/my-jetpack/_inc/data/constants.ts @@ -40,6 +40,7 @@ export const PRODUCT_SLUGS = { ANTI_SPAM: 'anti-spam', BACKUP: 'backup', BOOST: 'boost', + BRUTE_FORCE: 'brute-force', CRM: 'crm', CREATOR: 'creator', EXTRAS: 'extras', diff --git a/projects/packages/my-jetpack/global.d.ts b/projects/packages/my-jetpack/global.d.ts index 5ae0a3a54fcf6..3957602bcec8f 100644 --- a/projects/packages/my-jetpack/global.d.ts +++ b/projects/packages/my-jetpack/global.d.ts @@ -31,6 +31,7 @@ type JetpackModule = | 'anti-spam' | 'backup' | 'boost' + | 'feature_brute-force' | 'crm' | 'creator' | 'extras' diff --git a/projects/packages/my-jetpack/src/products/class-brute-force.php b/projects/packages/my-jetpack/src/products/class-brute-force.php new file mode 100644 index 0000000000000..d2adca6e3d6a2 --- /dev/null +++ b/projects/packages/my-jetpack/src/products/class-brute-force.php @@ -0,0 +1,153 @@ + true, + 'is_free' => true, + ); + } + + /** + * Checks whether the Product is active. + * If Jetpack plugin is active, then Extras will be inactive. + * + * @return boolean + */ + public static function is_active() { + return static::is_jetpack_plugin_active(); + } + + /** + * Checks whether the plugin is installed + * If Jetpack plugin is installed, then Extras will be inactive. + * + * @return boolean + */ + public static function is_plugin_installed() { + return static::is_jetpack_plugin_installed(); + } + + /** + * Get the URL where the user manages the product + * + * @return ?string + */ + public static function get_manage_url() { + return admin_url( 'admin.php?page=jetpack' ); + } + + /** + * Activates the Jetpack plugin + * + * @return null|WP_Error Null on success, WP_Error on invalid file. + */ + public static function activate_plugin() { + return activate_plugin( static::get_installed_plugin_filename( 'jetpack' ) ); + } +} From a68dd9ad3b04c478723e3987b5fa1e49f9326c06 Mon Sep 17 00:00:00 2001 From: Ian Ramos <5714212+IanRamosC@users.noreply.github.com> Date: Thu, 12 Dec 2024 23:07:33 -0300 Subject: [PATCH 2/8] Add config for feature modules --- .../evaluation-recommendations/index.tsx | 3 ++- .../components/product-cards-section/all.ts | 10 +++++++--- ...ute-force-card.tsx => newsletter-card.tsx} | 8 ++++---- .../related-posts-card.tsx | 20 +++++++++++++++++++ .../site-accelerator-card.tsx | 20 +++++++++++++++++++ .../my-jetpack/_inc/data/constants.ts | 9 ++++++--- projects/packages/my-jetpack/global.d.ts | 12 ++++++----- 7 files changed, 66 insertions(+), 16 deletions(-) rename projects/packages/my-jetpack/_inc/components/product-cards-section/{brute-force-card.tsx => newsletter-card.tsx} (60%) create mode 100644 projects/packages/my-jetpack/_inc/components/product-cards-section/related-posts-card.tsx create mode 100644 projects/packages/my-jetpack/_inc/components/product-cards-section/site-accelerator-card.tsx diff --git a/projects/packages/my-jetpack/_inc/components/evaluation-recommendations/index.tsx b/projects/packages/my-jetpack/_inc/components/evaluation-recommendations/index.tsx index 9716c4c9df675..0bb115f658e9e 100644 --- a/projects/packages/my-jetpack/_inc/components/evaluation-recommendations/index.tsx +++ b/projects/packages/my-jetpack/_inc/components/evaluation-recommendations/index.tsx @@ -131,7 +131,8 @@ const EvaluationRecommendations: FC = () => { fluid > { recommendedModules.map( module => { - const Card = JetpackModuleToProductCard[ module ]; + const moduleName = module.replace( 'feature_', '' ); + const Card = JetpackModuleToProductCard[ moduleName ]; return ( Card && ( diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/all.ts b/projects/packages/my-jetpack/_inc/components/product-cards-section/all.ts index a2357f58c9cd0..19a5c3cf4fd5a 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/all.ts +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/all.ts @@ -2,13 +2,15 @@ import AiCard from './ai-card'; import AntiSpamCard from './anti-spam-card'; import BackupCard from './backup-card'; import BoostCard from './boost-card'; -import BruteForceCard from './brute-force-card'; import CompleteCard from './complete-card'; import CrmCard from './crm-card'; import GrowthCard from './growth-card'; +import NewsletterCard from './newsletter-card'; import ProtectCard from './protect-card'; +import RelatedPostsCard from './related-posts-card'; import SearchCard from './search-card'; import SecurityCard from './security-card'; +import SiteAcceleratorCard from './site-accelerator-card'; import SocialCard from './social-card'; import StatsCard from './stats-card'; import VideopressCard from './videopress-card'; @@ -30,10 +32,12 @@ export const JetpackModuleToProductCard: { security: SecurityCard, growth: GrowthCard, complete: CompleteCard, - // Features: - 'feature_brute-force': BruteForceCard, // Not existing: extras: null, scan: null, creator: null, + // Features: + newsletter: NewsletterCard, + 'related-posts': RelatedPostsCard, + 'site-accelerator': SiteAcceleratorCard, }; diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/brute-force-card.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/newsletter-card.tsx similarity index 60% rename from projects/packages/my-jetpack/_inc/components/product-cards-section/brute-force-card.tsx rename to projects/packages/my-jetpack/_inc/components/product-cards-section/newsletter-card.tsx index f668f9df8c836..d3273867cdc9f 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/brute-force-card.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/newsletter-card.tsx @@ -2,19 +2,19 @@ import { PRODUCT_SLUGS } from '../../data/constants'; import ProductCard from '../connected-product-card'; import type { FC } from 'react'; -interface BruteForceCardProps { +interface NewsletterCardProps { admin?: boolean; recommendation?: boolean; } -const CompleteCard: FC< BruteForceCardProps > = ( { admin, recommendation } ) => { +const NewsletterCard: FC< NewsletterCardProps > = ( { admin, recommendation } ) => { return ( ); }; -export default CompleteCard; +export default NewsletterCard; diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/related-posts-card.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/related-posts-card.tsx new file mode 100644 index 0000000000000..3e94849ae406f --- /dev/null +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/related-posts-card.tsx @@ -0,0 +1,20 @@ +import { PRODUCT_SLUGS } from '../../data/constants'; +import ProductCard from '../connected-product-card'; +import type { FC } from 'react'; + +interface RelatedPostsCardProps { + admin?: boolean; + recommendation?: boolean; +} + +const RelatedPostsCard: FC< RelatedPostsCardProps > = ( { admin, recommendation } ) => { + return ( + + ); +}; + +export default RelatedPostsCard; diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/site-accelerator-card.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/site-accelerator-card.tsx new file mode 100644 index 0000000000000..a4ee55af84cb1 --- /dev/null +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/site-accelerator-card.tsx @@ -0,0 +1,20 @@ +import { PRODUCT_SLUGS } from '../../data/constants'; +import ProductCard from '../connected-product-card'; +import type { FC } from 'react'; + +interface SiteAcceleratorCardProps { + admin?: boolean; + recommendation?: boolean; +} + +const SiteAcceleratorCard: FC< SiteAcceleratorCardProps > = ( { admin, recommendation } ) => { + return ( + + ); +}; + +export default SiteAcceleratorCard; diff --git a/projects/packages/my-jetpack/_inc/data/constants.ts b/projects/packages/my-jetpack/_inc/data/constants.ts index ffe2ce63a9b38..bf718b02dc97d 100644 --- a/projects/packages/my-jetpack/_inc/data/constants.ts +++ b/projects/packages/my-jetpack/_inc/data/constants.ts @@ -45,13 +45,16 @@ export const PRODUCT_SLUGS = { CREATOR: 'creator', EXTRAS: 'extras', JETPACK_AI: 'jetpack-ai', + NEWSLETTER: 'newsletter', + PROTECT: 'protect', + RELATED_POSTS: 'related-posts', SCAN: 'scan', SEARCH: 'search', + SITE_ACCELERATOR: 'site-accelerator', SOCIAL: 'social', - SECURITY: 'security', - PROTECT: 'protect', - VIDEOPRESS: 'videopress', STATS: 'stats', + VIDEOPRESS: 'videopress', + SECURITY: 'security', GROWTH: 'growth', COMPLETE: 'complete', } satisfies Record< string, JetpackModule >; diff --git a/projects/packages/my-jetpack/global.d.ts b/projects/packages/my-jetpack/global.d.ts index 3957602bcec8f..3630bf179cdd9 100644 --- a/projects/packages/my-jetpack/global.d.ts +++ b/projects/packages/my-jetpack/global.d.ts @@ -31,21 +31,23 @@ type JetpackModule = | 'anti-spam' | 'backup' | 'boost' - | 'feature_brute-force' | 'crm' | 'creator' | 'extras' | 'ai' | 'jetpack-ai' + | 'protect' | 'scan' | 'search' | 'social' - | 'security' - | 'protect' - | 'videopress' | 'stats' + | 'videopress' + | 'security' | 'growth' - | 'complete'; + | 'complete' + | 'site-accelerator' + | 'newsletter' + | 'related-posts'; type ThreatItem = { // Protect API properties (free plan) From f8e544f23da7868bc7c4f5d571edf1af8c0e7757 Mon Sep 17 00:00:00 2001 From: Ian Ramos <5714212+IanRamosC@users.noreply.github.com> Date: Sun, 15 Dec 2024 23:04:40 -0300 Subject: [PATCH 3/8] Refactor module product class --- .../evaluation-recommendations/index.tsx | 3 +- .../components/product-cards-section/all.ts | 7 - .../product-cards-section/newsletter-card.tsx | 20 --- .../related-posts-card.tsx | 20 --- .../site-accelerator-card.tsx | 20 --- .../my-jetpack/_inc/data/constants.ts | 10 +- projects/packages/my-jetpack/global.d.ts | 11 +- .../src/products/class-brute-force.php | 153 ------------------ 8 files changed, 8 insertions(+), 236 deletions(-) delete mode 100644 projects/packages/my-jetpack/_inc/components/product-cards-section/newsletter-card.tsx delete mode 100644 projects/packages/my-jetpack/_inc/components/product-cards-section/related-posts-card.tsx delete mode 100644 projects/packages/my-jetpack/_inc/components/product-cards-section/site-accelerator-card.tsx delete mode 100644 projects/packages/my-jetpack/src/products/class-brute-force.php diff --git a/projects/packages/my-jetpack/_inc/components/evaluation-recommendations/index.tsx b/projects/packages/my-jetpack/_inc/components/evaluation-recommendations/index.tsx index 0bb115f658e9e..9716c4c9df675 100644 --- a/projects/packages/my-jetpack/_inc/components/evaluation-recommendations/index.tsx +++ b/projects/packages/my-jetpack/_inc/components/evaluation-recommendations/index.tsx @@ -131,8 +131,7 @@ const EvaluationRecommendations: FC = () => { fluid > { recommendedModules.map( module => { - const moduleName = module.replace( 'feature_', '' ); - const Card = JetpackModuleToProductCard[ moduleName ]; + const Card = JetpackModuleToProductCard[ module ]; return ( Card && ( diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/all.ts b/projects/packages/my-jetpack/_inc/components/product-cards-section/all.ts index 19a5c3cf4fd5a..18169a3e57272 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/all.ts +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/all.ts @@ -5,12 +5,9 @@ import BoostCard from './boost-card'; import CompleteCard from './complete-card'; import CrmCard from './crm-card'; import GrowthCard from './growth-card'; -import NewsletterCard from './newsletter-card'; import ProtectCard from './protect-card'; -import RelatedPostsCard from './related-posts-card'; import SearchCard from './search-card'; import SecurityCard from './security-card'; -import SiteAcceleratorCard from './site-accelerator-card'; import SocialCard from './social-card'; import StatsCard from './stats-card'; import VideopressCard from './videopress-card'; @@ -36,8 +33,4 @@ export const JetpackModuleToProductCard: { extras: null, scan: null, creator: null, - // Features: - newsletter: NewsletterCard, - 'related-posts': RelatedPostsCard, - 'site-accelerator': SiteAcceleratorCard, }; diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/newsletter-card.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/newsletter-card.tsx deleted file mode 100644 index d3273867cdc9f..0000000000000 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/newsletter-card.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { PRODUCT_SLUGS } from '../../data/constants'; -import ProductCard from '../connected-product-card'; -import type { FC } from 'react'; - -interface NewsletterCardProps { - admin?: boolean; - recommendation?: boolean; -} - -const NewsletterCard: FC< NewsletterCardProps > = ( { admin, recommendation } ) => { - return ( - - ); -}; - -export default NewsletterCard; diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/related-posts-card.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/related-posts-card.tsx deleted file mode 100644 index 3e94849ae406f..0000000000000 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/related-posts-card.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { PRODUCT_SLUGS } from '../../data/constants'; -import ProductCard from '../connected-product-card'; -import type { FC } from 'react'; - -interface RelatedPostsCardProps { - admin?: boolean; - recommendation?: boolean; -} - -const RelatedPostsCard: FC< RelatedPostsCardProps > = ( { admin, recommendation } ) => { - return ( - - ); -}; - -export default RelatedPostsCard; diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/site-accelerator-card.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/site-accelerator-card.tsx deleted file mode 100644 index a4ee55af84cb1..0000000000000 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/site-accelerator-card.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { PRODUCT_SLUGS } from '../../data/constants'; -import ProductCard from '../connected-product-card'; -import type { FC } from 'react'; - -interface SiteAcceleratorCardProps { - admin?: boolean; - recommendation?: boolean; -} - -const SiteAcceleratorCard: FC< SiteAcceleratorCardProps > = ( { admin, recommendation } ) => { - return ( - - ); -}; - -export default SiteAcceleratorCard; diff --git a/projects/packages/my-jetpack/_inc/data/constants.ts b/projects/packages/my-jetpack/_inc/data/constants.ts index bf718b02dc97d..c5dd1a84a63f7 100644 --- a/projects/packages/my-jetpack/_inc/data/constants.ts +++ b/projects/packages/my-jetpack/_inc/data/constants.ts @@ -40,21 +40,17 @@ export const PRODUCT_SLUGS = { ANTI_SPAM: 'anti-spam', BACKUP: 'backup', BOOST: 'boost', - BRUTE_FORCE: 'brute-force', CRM: 'crm', CREATOR: 'creator', EXTRAS: 'extras', JETPACK_AI: 'jetpack-ai', - NEWSLETTER: 'newsletter', - PROTECT: 'protect', - RELATED_POSTS: 'related-posts', SCAN: 'scan', SEARCH: 'search', - SITE_ACCELERATOR: 'site-accelerator', SOCIAL: 'social', - STATS: 'stats', - VIDEOPRESS: 'videopress', SECURITY: 'security', + PROTECT: 'protect', + VIDEOPRESS: 'videopress', + STATS: 'stats', GROWTH: 'growth', COMPLETE: 'complete', } satisfies Record< string, JetpackModule >; diff --git a/projects/packages/my-jetpack/global.d.ts b/projects/packages/my-jetpack/global.d.ts index 3630bf179cdd9..5ae0a3a54fcf6 100644 --- a/projects/packages/my-jetpack/global.d.ts +++ b/projects/packages/my-jetpack/global.d.ts @@ -36,18 +36,15 @@ type JetpackModule = | 'extras' | 'ai' | 'jetpack-ai' - | 'protect' | 'scan' | 'search' | 'social' - | 'stats' - | 'videopress' | 'security' + | 'protect' + | 'videopress' + | 'stats' | 'growth' - | 'complete' - | 'site-accelerator' - | 'newsletter' - | 'related-posts'; + | 'complete'; type ThreatItem = { // Protect API properties (free plan) diff --git a/projects/packages/my-jetpack/src/products/class-brute-force.php b/projects/packages/my-jetpack/src/products/class-brute-force.php deleted file mode 100644 index d2adca6e3d6a2..0000000000000 --- a/projects/packages/my-jetpack/src/products/class-brute-force.php +++ /dev/null @@ -1,153 +0,0 @@ - true, - 'is_free' => true, - ); - } - - /** - * Checks whether the Product is active. - * If Jetpack plugin is active, then Extras will be inactive. - * - * @return boolean - */ - public static function is_active() { - return static::is_jetpack_plugin_active(); - } - - /** - * Checks whether the plugin is installed - * If Jetpack plugin is installed, then Extras will be inactive. - * - * @return boolean - */ - public static function is_plugin_installed() { - return static::is_jetpack_plugin_installed(); - } - - /** - * Get the URL where the user manages the product - * - * @return ?string - */ - public static function get_manage_url() { - return admin_url( 'admin.php?page=jetpack' ); - } - - /** - * Activates the Jetpack plugin - * - * @return null|WP_Error Null on success, WP_Error on invalid file. - */ - public static function activate_plugin() { - return activate_plugin( static::get_installed_plugin_filename( 'jetpack' ) ); - } -} From 5a078454531c43f29e1b1dc46b360440992a8df3 Mon Sep 17 00:00:00 2001 From: Ian Ramos <5714212+IanRamosC@users.noreply.github.com> Date: Mon, 16 Dec 2024 23:22:15 -0300 Subject: [PATCH 4/8] Add basic structure for feature recommendations card --- .../evaluation-recommendations/index.tsx | 3 ++- .../components/product-cards-section/all.ts | 7 +++++++ .../product-cards-section/newsletter-card.tsx | 20 +++++++++++++++++++ .../related-posts-card.tsx | 20 +++++++++++++++++++ .../site-accelerator-card.tsx | 20 +++++++++++++++++++ .../my-jetpack/_inc/data/constants.ts | 10 +++++++--- projects/packages/my-jetpack/global.d.ts | 11 ++++++---- 7 files changed, 83 insertions(+), 8 deletions(-) create mode 100644 projects/packages/my-jetpack/_inc/components/product-cards-section/newsletter-card.tsx create mode 100644 projects/packages/my-jetpack/_inc/components/product-cards-section/related-posts-card.tsx create mode 100644 projects/packages/my-jetpack/_inc/components/product-cards-section/site-accelerator-card.tsx diff --git a/projects/packages/my-jetpack/_inc/components/evaluation-recommendations/index.tsx b/projects/packages/my-jetpack/_inc/components/evaluation-recommendations/index.tsx index 9716c4c9df675..0bb115f658e9e 100644 --- a/projects/packages/my-jetpack/_inc/components/evaluation-recommendations/index.tsx +++ b/projects/packages/my-jetpack/_inc/components/evaluation-recommendations/index.tsx @@ -131,7 +131,8 @@ const EvaluationRecommendations: FC = () => { fluid > { recommendedModules.map( module => { - const Card = JetpackModuleToProductCard[ module ]; + const moduleName = module.replace( 'feature_', '' ); + const Card = JetpackModuleToProductCard[ moduleName ]; return ( Card && ( diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/all.ts b/projects/packages/my-jetpack/_inc/components/product-cards-section/all.ts index 18169a3e57272..19a5c3cf4fd5a 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/all.ts +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/all.ts @@ -5,9 +5,12 @@ import BoostCard from './boost-card'; import CompleteCard from './complete-card'; import CrmCard from './crm-card'; import GrowthCard from './growth-card'; +import NewsletterCard from './newsletter-card'; import ProtectCard from './protect-card'; +import RelatedPostsCard from './related-posts-card'; import SearchCard from './search-card'; import SecurityCard from './security-card'; +import SiteAcceleratorCard from './site-accelerator-card'; import SocialCard from './social-card'; import StatsCard from './stats-card'; import VideopressCard from './videopress-card'; @@ -33,4 +36,8 @@ export const JetpackModuleToProductCard: { extras: null, scan: null, creator: null, + // Features: + newsletter: NewsletterCard, + 'related-posts': RelatedPostsCard, + 'site-accelerator': SiteAcceleratorCard, }; diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/newsletter-card.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/newsletter-card.tsx new file mode 100644 index 0000000000000..d3273867cdc9f --- /dev/null +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/newsletter-card.tsx @@ -0,0 +1,20 @@ +import { PRODUCT_SLUGS } from '../../data/constants'; +import ProductCard from '../connected-product-card'; +import type { FC } from 'react'; + +interface NewsletterCardProps { + admin?: boolean; + recommendation?: boolean; +} + +const NewsletterCard: FC< NewsletterCardProps > = ( { admin, recommendation } ) => { + return ( + + ); +}; + +export default NewsletterCard; diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/related-posts-card.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/related-posts-card.tsx new file mode 100644 index 0000000000000..3e94849ae406f --- /dev/null +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/related-posts-card.tsx @@ -0,0 +1,20 @@ +import { PRODUCT_SLUGS } from '../../data/constants'; +import ProductCard from '../connected-product-card'; +import type { FC } from 'react'; + +interface RelatedPostsCardProps { + admin?: boolean; + recommendation?: boolean; +} + +const RelatedPostsCard: FC< RelatedPostsCardProps > = ( { admin, recommendation } ) => { + return ( + + ); +}; + +export default RelatedPostsCard; diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/site-accelerator-card.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/site-accelerator-card.tsx new file mode 100644 index 0000000000000..a4ee55af84cb1 --- /dev/null +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/site-accelerator-card.tsx @@ -0,0 +1,20 @@ +import { PRODUCT_SLUGS } from '../../data/constants'; +import ProductCard from '../connected-product-card'; +import type { FC } from 'react'; + +interface SiteAcceleratorCardProps { + admin?: boolean; + recommendation?: boolean; +} + +const SiteAcceleratorCard: FC< SiteAcceleratorCardProps > = ( { admin, recommendation } ) => { + return ( + + ); +}; + +export default SiteAcceleratorCard; diff --git a/projects/packages/my-jetpack/_inc/data/constants.ts b/projects/packages/my-jetpack/_inc/data/constants.ts index c5dd1a84a63f7..bf718b02dc97d 100644 --- a/projects/packages/my-jetpack/_inc/data/constants.ts +++ b/projects/packages/my-jetpack/_inc/data/constants.ts @@ -40,17 +40,21 @@ export const PRODUCT_SLUGS = { ANTI_SPAM: 'anti-spam', BACKUP: 'backup', BOOST: 'boost', + BRUTE_FORCE: 'brute-force', CRM: 'crm', CREATOR: 'creator', EXTRAS: 'extras', JETPACK_AI: 'jetpack-ai', + NEWSLETTER: 'newsletter', + PROTECT: 'protect', + RELATED_POSTS: 'related-posts', SCAN: 'scan', SEARCH: 'search', + SITE_ACCELERATOR: 'site-accelerator', SOCIAL: 'social', - SECURITY: 'security', - PROTECT: 'protect', - VIDEOPRESS: 'videopress', STATS: 'stats', + VIDEOPRESS: 'videopress', + SECURITY: 'security', GROWTH: 'growth', COMPLETE: 'complete', } satisfies Record< string, JetpackModule >; diff --git a/projects/packages/my-jetpack/global.d.ts b/projects/packages/my-jetpack/global.d.ts index 5ae0a3a54fcf6..3630bf179cdd9 100644 --- a/projects/packages/my-jetpack/global.d.ts +++ b/projects/packages/my-jetpack/global.d.ts @@ -36,15 +36,18 @@ type JetpackModule = | 'extras' | 'ai' | 'jetpack-ai' + | 'protect' | 'scan' | 'search' | 'social' - | 'security' - | 'protect' - | 'videopress' | 'stats' + | 'videopress' + | 'security' | 'growth' - | 'complete'; + | 'complete' + | 'site-accelerator' + | 'newsletter' + | 'related-posts'; type ThreatItem = { // Protect API properties (free plan) From 60f51b1383201fb1e29febe9962e099ef0168b33 Mon Sep 17 00:00:00 2001 From: Ian Ramos <5714212+IanRamosC@users.noreply.github.com> Date: Wed, 18 Dec 2024 00:13:47 -0300 Subject: [PATCH 5/8] Add actions for each status --- .../product-card/recommendation-actions.tsx | 16 +++--- .../product-card/use-pricing-data.ts | 55 ++++++++++++++++++- projects/packages/my-jetpack/global.d.ts | 1 + .../src/products/class-module-product.php | 7 --- .../my-jetpack/src/products/class-product.php | 8 +++ 5 files changed, 71 insertions(+), 16 deletions(-) diff --git a/projects/packages/my-jetpack/_inc/components/product-card/recommendation-actions.tsx b/projects/packages/my-jetpack/_inc/components/product-card/recommendation-actions.tsx index c0d84ace2116b..8a05af0d71b51 100644 --- a/projects/packages/my-jetpack/_inc/components/product-card/recommendation-actions.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-card/recommendation-actions.tsx @@ -4,19 +4,21 @@ import styles from './style.module.scss'; import usePricingData from './use-pricing-data'; const RecommendationActions = ( { slug }: { slug: string } ) => { - const { secondaryAction, purchaseAction, isActivating } = usePricingData( slug ); + const { secondaryAction, primaryAction, isActivating } = usePricingData( slug ); return (
- { purchaseAction && ( - + ) } + { secondaryAction && ( + ) } -
); diff --git a/projects/packages/my-jetpack/_inc/components/product-card/use-pricing-data.ts b/projects/packages/my-jetpack/_inc/components/product-card/use-pricing-data.ts index 06e6beb1c01e9..aeda36f938185 100644 --- a/projects/packages/my-jetpack/_inc/components/product-card/use-pricing-data.ts +++ b/projects/packages/my-jetpack/_inc/components/product-card/use-pricing-data.ts @@ -8,6 +8,7 @@ import { ProductCamelCase } from '../../data/types'; import { getMyJetpackWindowInitialState } from '../../data/utils/get-my-jetpack-window-state'; import useAnalytics from '../../hooks/use-analytics'; import useMyJetpackConnection from '../../hooks/use-my-jetpack-connection'; +import useInstallStandalonePlugin from '../../data/products/use-install-standalone-plugin'; const parsePricingData = ( pricingForUi: ProductCamelCase[ 'pricingForUi' ] ) => { const { tiers, wpcomFreeProductSlug, introductoryOffer } = pricingForUi; @@ -52,13 +53,36 @@ const parsePricingData = ( pricingForUi: ProductCamelCase[ 'pricingForUi' ] ) => }; }; -const getPurchaseAction = ( detail: ProductCamelCase, onCheckout: () => void ) => { +// type for onCheckout and onActivate +type Actions = { + onCheckout: () => void; + onActivate: () => void; + onInstall: () => void; + onManage: () => void; +}; + +const getPrimaryAction = ( + detail: ProductCamelCase, + { onCheckout, onActivate, onInstall, onManage }: Actions +) => { const isUpgradable = detail.status === PRODUCT_STATUSES.ACTIVE && ( detail.isUpgradableByBundle.length || detail.isUpgradable ); const upgradeHasPrice = detail.pricingForUi.fullPrice || detail.pricingForUi.tiers?.upgraded?.fullPrice; + if ( detail.status === PRODUCT_STATUSES.MODULE_DISABLED ) { + return { label: __( 'Activate', 'jetpack-my-jetpack' ), onClick: onActivate }; + } + + if ( detail.status === PRODUCT_STATUSES.ABSENT ) { + return { label: __( 'Install', 'jetpack-my-jetpack' ), onClick: onInstall }; + } + + if ( detail.status === PRODUCT_STATUSES.USER_CONNECTION_ERROR ) { + return { label: __( 'Connect', 'jetpack-my-jetpack' ), href: '#/connection' }; + } + if ( detail.status === PRODUCT_STATUSES.CAN_UPGRADE || isUpgradable ) { if ( upgradeHasPrice ) { return { label: __( 'Upgrade', 'jetpack-my-jetpack' ), onClick: onCheckout }; @@ -66,6 +90,14 @@ const getPurchaseAction = ( detail: ProductCamelCase, onCheckout: () => void ) = return null; } + if ( detail.isFeature ) { + return { + label: __( 'Manage', 'jetpack-my-jetpack' ), + href: detail.manageUrl, + onClick: onCheckout, + }; + } + return { label: __( 'Purchase', 'jetpack-my-jetpack' ), onClick: onCheckout }; }; @@ -98,6 +130,7 @@ const usePricingData = ( slug: string ) => { const { wpcomProductSlug, wpcomFreeProductSlug, ...data } = parsePricingData( detail.pricingForUi ); + const { install: installPlugin, isPending: isInstalling } = useInstallStandalonePlugin( slug ); const { isUserConnected } = useMyJetpackConnection(); const { myJetpackUrl, siteSuffix } = getMyJetpackWindowInitialState(); @@ -135,9 +168,27 @@ const usePricingData = ( slug: string ) => { runCheckout(); }, [ activate, recordEvent, runCheckout, slug ] ); + const handleInstall = useCallback( () => { + recordEvent( 'jetpack_myjetpack_evaluation_recommendations_install_plugin_click', { + product: slug, + } ); + installPlugin(); + }, [ slug, installPlugin, recordEvent ] ); + + const handleManage = useCallback( () => { + recordEvent( 'jetpack_myjetpack_evaluation_recommendations_manage_click', { + product: slug, + } ); + }, [ slug, recordEvent ] ); + return { secondaryAction: getSecondaryAction( detail, handleActivate ), - purchaseAction: getPurchaseAction( detail, handleCheckout ), + primaryAction: getPrimaryAction( detail, { + onCheckout: handleCheckout, + onActivate: handleActivate, + onInstall: handleInstall, + onManage: handleManage, + } ), isActivating, ...data, }; diff --git a/projects/packages/my-jetpack/global.d.ts b/projects/packages/my-jetpack/global.d.ts index 3630bf179cdd9..5efae306c737d 100644 --- a/projects/packages/my-jetpack/global.d.ts +++ b/projects/packages/my-jetpack/global.d.ts @@ -177,6 +177,7 @@ interface Window { has_paid_plan_for_product: boolean; features_by_tier: Array< string >; is_bundle: boolean; + is_feature: boolean; is_plugin_active: boolean; is_upgradable: boolean; is_upgradable_by_bundle: string[]; diff --git a/projects/packages/my-jetpack/src/products/class-module-product.php b/projects/packages/my-jetpack/src/products/class-module-product.php index 9d1a14f0fee86..40e4ef7f4c6d5 100644 --- a/projects/packages/my-jetpack/src/products/class-module-product.php +++ b/projects/packages/my-jetpack/src/products/class-module-product.php @@ -27,13 +27,6 @@ abstract class Module_Product extends Product { */ public static $module_name = null; - /** - * Whether this module is a Jetpack feature - * - * @var boolean - */ - public static $is_feature = false; - /** * Get the plugin slug - ovewrite it ans return Jetpack's * diff --git a/projects/packages/my-jetpack/src/products/class-product.php b/projects/packages/my-jetpack/src/products/class-product.php index b2262eddd6b4a..de36694475714 100644 --- a/projects/packages/my-jetpack/src/products/class-product.php +++ b/projects/packages/my-jetpack/src/products/class-product.php @@ -73,6 +73,13 @@ abstract class Product { */ const EXPIRATION_CUTOFF_TIME = '+2 months'; + /** + * Whether this module is a Jetpack feature + * + * @var boolean + */ + public static $is_feature = false; + /** * Whether this product requires a site connection * @@ -182,6 +189,7 @@ public static function get_info() { 'is_plugin_active' => static::is_plugin_active(), 'is_upgradable' => static::is_upgradable(), 'is_upgradable_by_bundle' => static::is_upgradable_by_bundle(), + 'is_feature' => static::$is_feature, 'supported_products' => static::get_supported_products(), 'wpcom_product_slug' => static::get_wpcom_product_slug(), 'requires_user_connection' => static::$requires_user_connection, From c12225d9be7902019e4f4fff31b722f3c31efdd0 Mon Sep 17 00:00:00 2001 From: Ian Ramos <5714212+IanRamosC@users.noreply.github.com> Date: Sun, 22 Dec 2024 22:54:25 -0300 Subject: [PATCH 6/8] changelog --- .../my-jetpack/changelog/add-feature-recommendations-cards | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/packages/my-jetpack/changelog/add-feature-recommendations-cards diff --git a/projects/packages/my-jetpack/changelog/add-feature-recommendations-cards b/projects/packages/my-jetpack/changelog/add-feature-recommendations-cards new file mode 100644 index 0000000000000..38a8435a85bd8 --- /dev/null +++ b/projects/packages/my-jetpack/changelog/add-feature-recommendations-cards @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +My Jetpack: introduce feature cards for recommendations in My Jetpack. From 5474ad5ba1a3128154e812503c6d26603051dc21 Mon Sep 17 00:00:00 2001 From: Ian Ramos <5714212+IanRamosC@users.noreply.github.com> Date: Mon, 23 Dec 2024 11:32:09 -0300 Subject: [PATCH 7/8] Add return type for activate_plugin method --- projects/packages/my-jetpack/src/products/class-newsletter.php | 2 +- .../packages/my-jetpack/src/products/class-related-posts.php | 2 +- .../packages/my-jetpack/src/products/class-site-accelerator.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/projects/packages/my-jetpack/src/products/class-newsletter.php b/projects/packages/my-jetpack/src/products/class-newsletter.php index 66d806d5ea6ca..61a5736b77c61 100644 --- a/projects/packages/my-jetpack/src/products/class-newsletter.php +++ b/projects/packages/my-jetpack/src/products/class-newsletter.php @@ -169,7 +169,7 @@ public static function get_manage_url() { * * @return null|WP_Error Null on success, WP_Error on invalid file. */ - public static function activate_plugin() { + public static function activate_plugin(): ?WP_Error { $plugin_filename = static::get_installed_plugin_filename( self::JETPACK_PLUGIN_SLUG ); if ( $plugin_filename ) { diff --git a/projects/packages/my-jetpack/src/products/class-related-posts.php b/projects/packages/my-jetpack/src/products/class-related-posts.php index e5b10ec989931..a12a5715b2a91 100644 --- a/projects/packages/my-jetpack/src/products/class-related-posts.php +++ b/projects/packages/my-jetpack/src/products/class-related-posts.php @@ -169,7 +169,7 @@ public static function get_manage_url() { * * @return null|WP_Error Null on success, WP_Error on invalid file. */ - public static function activate_plugin() { + public static function activate_plugin(): ?WP_Error { $plugin_filename = static::get_installed_plugin_filename( self::JETPACK_PLUGIN_SLUG ); if ( $plugin_filename ) { diff --git a/projects/packages/my-jetpack/src/products/class-site-accelerator.php b/projects/packages/my-jetpack/src/products/class-site-accelerator.php index 42cda9bc8a798..72e65b9ff3d49 100644 --- a/projects/packages/my-jetpack/src/products/class-site-accelerator.php +++ b/projects/packages/my-jetpack/src/products/class-site-accelerator.php @@ -169,7 +169,7 @@ public static function get_manage_url() { * * @return null|WP_Error Null on success, WP_Error on invalid file. */ - public static function activate_plugin() { + public static function activate_plugin(): ?WP_Error { $plugin_filename = static::get_installed_plugin_filename( self::JETPACK_PLUGIN_SLUG ); if ( $plugin_filename ) { From 4439504010b1bfc511e4d47f38ce7cd9e9a6a04e Mon Sep 17 00:00:00 2001 From: Ian Ramos <5714212+IanRamosC@users.noreply.github.com> Date: Mon, 23 Dec 2024 16:26:33 -0300 Subject: [PATCH 8/8] Cover free offerings in recommendations cards --- .../product-card/pricing-component.tsx | 15 ++++++--- .../product-card/recommendation-actions.tsx | 9 +++-- .../product-card/use-pricing-data.ts | 33 +++++++++++-------- 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/projects/packages/my-jetpack/_inc/components/product-card/pricing-component.tsx b/projects/packages/my-jetpack/_inc/components/product-card/pricing-component.tsx index e6af2ff237f4f..4c46c83f7ac89 100644 --- a/projects/packages/my-jetpack/_inc/components/product-card/pricing-component.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-card/pricing-component.tsx @@ -5,16 +5,23 @@ import styles from './style.module.scss'; import usePricingData from './use-pricing-data'; const PriceComponent = ( { slug }: { slug: string } ) => { - const { discountPrice, fullPrice, currencyCode } = usePricingData( slug ); + const { discountPrice, fullPrice, currencyCode, isFeature, hasFreeOffering } = + usePricingData( slug ); + const isFreeFeature = isFeature && hasFreeOffering && ! fullPrice; return (
{ discountPrice && ( { formatCurrency( discountPrice, currencyCode ) } ) } - - { formatCurrency( fullPrice, currencyCode ) } + + { ! isFreeFeature && formatCurrency( fullPrice, currencyCode ) } + { isFreeFeature && __( 'Free', 'jetpack-my-jetpack' ) } - { __( '/month, billed yearly', 'jetpack-my-jetpack' ) } + { ! isFreeFeature && ( + + { __( '/month, billed yearly', 'jetpack-my-jetpack' ) } + + ) }
); }; diff --git a/projects/packages/my-jetpack/_inc/components/product-card/recommendation-actions.tsx b/projects/packages/my-jetpack/_inc/components/product-card/recommendation-actions.tsx index 8a05af0d71b51..e01ebd9ba9b8c 100644 --- a/projects/packages/my-jetpack/_inc/components/product-card/recommendation-actions.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-card/recommendation-actions.tsx @@ -4,13 +4,18 @@ import styles from './style.module.scss'; import usePricingData from './use-pricing-data'; const RecommendationActions = ( { slug }: { slug: string } ) => { - const { secondaryAction, primaryAction, isActivating } = usePricingData( slug ); + const { secondaryAction, primaryAction, isFeature, isActivating, isInstalling } = + usePricingData( slug ); return (
{ primaryAction && ( - ) } diff --git a/projects/packages/my-jetpack/_inc/components/product-card/use-pricing-data.ts b/projects/packages/my-jetpack/_inc/components/product-card/use-pricing-data.ts index aeda36f938185..c7f3f0d751c4f 100644 --- a/projects/packages/my-jetpack/_inc/components/product-card/use-pricing-data.ts +++ b/projects/packages/my-jetpack/_inc/components/product-card/use-pricing-data.ts @@ -3,12 +3,12 @@ import { __ } from '@wordpress/i18n'; import { useCallback } from 'react'; import { PRODUCT_STATUSES } from '../../constants'; import useActivate from '../../data/products/use-activate'; +import useInstallStandalonePlugin from '../../data/products/use-install-standalone-plugin'; import useProduct from '../../data/products/use-product'; import { ProductCamelCase } from '../../data/types'; import { getMyJetpackWindowInitialState } from '../../data/utils/get-my-jetpack-window-state'; import useAnalytics from '../../hooks/use-analytics'; import useMyJetpackConnection from '../../hooks/use-my-jetpack-connection'; -import useInstallStandalonePlugin from '../../data/products/use-install-standalone-plugin'; const parsePricingData = ( pricingForUi: ProductCamelCase[ 'pricingForUi' ] ) => { const { tiers, wpcomFreeProductSlug, introductoryOffer } = pricingForUi; @@ -71,18 +71,6 @@ const getPrimaryAction = ( const upgradeHasPrice = detail.pricingForUi.fullPrice || detail.pricingForUi.tiers?.upgraded?.fullPrice; - if ( detail.status === PRODUCT_STATUSES.MODULE_DISABLED ) { - return { label: __( 'Activate', 'jetpack-my-jetpack' ), onClick: onActivate }; - } - - if ( detail.status === PRODUCT_STATUSES.ABSENT ) { - return { label: __( 'Install', 'jetpack-my-jetpack' ), onClick: onInstall }; - } - - if ( detail.status === PRODUCT_STATUSES.USER_CONNECTION_ERROR ) { - return { label: __( 'Connect', 'jetpack-my-jetpack' ), href: '#/connection' }; - } - if ( detail.status === PRODUCT_STATUSES.CAN_UPGRADE || isUpgradable ) { if ( upgradeHasPrice ) { return { label: __( 'Upgrade', 'jetpack-my-jetpack' ), onClick: onCheckout }; @@ -91,10 +79,20 @@ const getPrimaryAction = ( } if ( detail.isFeature ) { + if ( detail.status === PRODUCT_STATUSES.MODULE_DISABLED ) { + return { label: __( 'Activate', 'jetpack-my-jetpack' ), onClick: onActivate }; + } + if ( detail.status === PRODUCT_STATUSES.ABSENT ) { + return { label: __( 'Install', 'jetpack-my-jetpack' ), onClick: onInstall }; + } + if ( detail.status === PRODUCT_STATUSES.USER_CONNECTION_ERROR ) { + return { label: __( 'Connect', 'jetpack-my-jetpack' ), href: '#/connection' }; + } + return { label: __( 'Manage', 'jetpack-my-jetpack' ), href: detail.manageUrl, - onClick: onCheckout, + onClick: onManage, }; } @@ -102,6 +100,10 @@ const getPrimaryAction = ( }; const getSecondaryAction = ( detail: ProductCamelCase, onActivate: () => void ) => { + if ( detail.isFeature ) { + return null; + } + const START_FOR_FREE_FEATURE_FLAG = false; const isNotActiveOrNeedsExplicitFreePlan = ! detail.isPluginActive || @@ -189,7 +191,10 @@ const usePricingData = ( slug: string ) => { onInstall: handleInstall, onManage: handleManage, } ), + isFeature: detail.isFeature, + hasFreeOffering: detail.hasFreeOffering, isActivating, + isInstalling, ...data, }; };