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 && (
-
);
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 && (
-
+
{ primaryAction.label }
) }
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,
};
};