1+ /**
2+ * External dependencies
3+ */
4+ import { isSimpleSite } from '@automattic/jetpack-script-data' ;
15import {
6+ Button ,
7+ ExternalLink ,
28 __experimentalText as Text , // eslint-disable-line @wordpress/no-unsafe-wp-apis
39 __experimentalVStack as VStack , // eslint-disable-line @wordpress/no-unsafe-wp-apis
410} from '@wordpress/components' ;
11+ import { useSelect , useDispatch } from '@wordpress/data' ;
12+ import { createInterpolateElement , useCallback , useMemo } from '@wordpress/element' ;
513import { __ , _n , sprintf } from '@wordpress/i18n' ;
14+ /**
15+ * Internal dependencies
16+ */
617import useConfigValue from '../../../hooks/use-config-value.ts' ;
18+ import { usePluginInstallation } from '../../../hooks/use-plugin-installation.ts' ;
19+ import { INTEGRATIONS_STORE } from '../../../store/integrations/index.ts' ;
720import CreateFormButton from '../create-form-button/index.tsx' ;
21+ /**
22+ * Types
23+ */
24+ import type {
25+ IntegrationsDispatch ,
26+ SelectIntegrations ,
27+ } from '../../../store/integrations/index.ts' ;
28+ import type { Integration } from '../../../types/index.ts' ;
29+ import type { ReactNode } from 'react' ;
830
9- const EmptyWrapper = ( { heading = '' , body = '' , actions = null } ) => (
31+ type UseInstallAkismetReturn = {
32+ shouldShowAkismetCta : boolean ;
33+ wrapperBody : ReactNode ;
34+ isInstallingAkismet : boolean ;
35+ canPerformAkismetAction : boolean ;
36+ wrapperButtonText : string ;
37+ handleAkismetSetup : ( ) => Promise < void > ;
38+ } ;
39+
40+ type EmptyResponsesProps = {
41+ status : string ;
42+ isSearch : boolean ;
43+ readStatusFilter ?: 'unread' | 'read' ;
44+ } ;
45+
46+ type EmptyWrapperProps = {
47+ heading ?: string ;
48+ body ?: string | ReactNode ;
49+ actions ?: ReactNode ;
50+ } ;
51+
52+ /**
53+ * Hook to handle Akismet installation and activation.
54+ *
55+ * @return {UseInstallAkismetReturn } An object containing the necessary data and functions to handle Akismet installation and activation.
56+ */
57+ const useInstallAkismet = ( ) : UseInstallAkismetReturn => {
58+ const { akismetIntegration } = useSelect ( ( select : SelectIntegrations ) => {
59+ const store = select ( INTEGRATIONS_STORE ) ;
60+ const integrations = store . getIntegrations ( ) || [ ] ;
61+
62+ return {
63+ akismetIntegration : integrations . find (
64+ ( integration : Integration ) => integration . id === 'akismet'
65+ ) ,
66+ } ;
67+ } , [ ] ) as { akismetIntegration ?: Integration } ;
68+
69+ const { refreshIntegrations } = useDispatch ( INTEGRATIONS_STORE ) as IntegrationsDispatch ;
70+
71+ const akismetIntegrationReady = useMemo (
72+ ( ) => ! ! akismetIntegration && ! akismetIntegration . __isPartial ,
73+ [ akismetIntegration ]
74+ ) ;
75+
76+ const isInstalled = ! ! akismetIntegration ?. isInstalled ;
77+
78+ const isAkismetActive = akismetIntegrationReady && isInstalled && ! ! akismetIntegration ?. isActive ;
79+
80+ const shouldShowAkismetCta = akismetIntegrationReady && ! isAkismetActive && ! isSimpleSite ( ) ;
81+
82+ const akismetPluginFile = useMemo (
83+ ( ) => akismetIntegration ?. pluginFile ?? 'akismet/akismet' ,
84+ [ akismetIntegration ?. pluginFile ]
85+ ) ;
86+
87+ const wrapperBody : ReactNode = createInterpolateElement (
88+ __ (
89+ 'Want automatic spam filtering? Akismet Anti-spam protects millions of sites. <moreInfoLink>Learn more.</moreInfoLink>' ,
90+ 'jetpack-forms'
91+ ) ,
92+ {
93+ moreInfoLink : < ExternalLink href = "https://akismet.com/" /> ,
94+ }
95+ ) ;
96+
97+ const activateButtonText = __ ( 'Activate Akismet Anti-spam' , 'jetpack-forms' ) ;
98+ const installAndActivateButtonText = __ ( 'Install Akismet Anti-spam' , 'jetpack-forms' ) ;
99+ const wrapperButtonText = isInstalled ? activateButtonText : installAndActivateButtonText ;
100+
101+ const {
102+ isInstalling : isInstallingAkismet ,
103+ installPlugin,
104+ canInstallPlugins,
105+ canActivatePlugins,
106+ } = usePluginInstallation ( {
107+ slug : 'akismet' ,
108+ pluginPath : akismetPluginFile ,
109+ isInstalled,
110+ onSuccess : refreshIntegrations ,
111+ trackEventName : 'jetpack_forms_upsell_akismet_click' ,
112+ trackEventProps : {
113+ screen : 'dashboard' ,
114+ } ,
115+ successNotices : {
116+ install : {
117+ message : __ ( 'Akismet installed and activated.' , 'jetpack-forms' ) ,
118+ options : { type : 'snackbar' , id : 'akismet-install-success' } ,
119+ } ,
120+ activate : {
121+ message : __ ( 'Akismet activated.' , 'jetpack-forms' ) ,
122+ options : { type : 'snackbar' , id : 'akismet-install-success' } ,
123+ } ,
124+ } ,
125+ errorNotice : {
126+ message : __ ( 'Could not set up Akismet. Please try again.' , 'jetpack-forms' ) ,
127+ options : { type : 'snackbar' , id : 'akismet-install-error' } ,
128+ } ,
129+ } ) ;
130+
131+ const canPerformAkismetAction =
132+ isInstalled && akismetIntegrationReady
133+ ? canActivatePlugins !== false
134+ : canInstallPlugins !== false ;
135+
136+ const handleAkismetSetup = useCallback ( async ( ) => {
137+ if ( isInstallingAkismet || ! akismetIntegrationReady || ! canPerformAkismetAction ) {
138+ return ;
139+ }
140+
141+ await installPlugin ( ) ;
142+ } , [ isInstallingAkismet , akismetIntegrationReady , canPerformAkismetAction , installPlugin ] ) ;
143+
144+ return {
145+ shouldShowAkismetCta,
146+ wrapperBody,
147+ isInstallingAkismet,
148+ canPerformAkismetAction,
149+ wrapperButtonText,
150+ handleAkismetSetup,
151+ } ;
152+ } ;
153+
154+ const EmptyWrapper = ( { heading = '' , body = '' , actions = null } : EmptyWrapperProps ) => (
10155 < VStack alignment = "center" spacing = "2" >
11156 { heading && (
12157 < Text as = "h3" weight = "500" size = "15" >
@@ -18,14 +163,16 @@ const EmptyWrapper = ( { heading = '', body = '', actions = null } ) => (
18163 </ VStack >
19164) ;
20165
21- type EmptyResponsesProps = {
22- status : string ;
23- isSearch : boolean ;
24- readStatusFilter ?: 'unread' | 'read' ;
25- } ;
26-
27166const EmptyResponses = ( { status, isSearch, readStatusFilter } : EmptyResponsesProps ) => {
28167 const emptyTrashDays = useConfigValue ( 'emptyTrashDays' ) ?? 0 ;
168+ const {
169+ shouldShowAkismetCta,
170+ wrapperBody,
171+ isInstallingAkismet,
172+ canPerformAkismetAction,
173+ wrapperButtonText,
174+ handleAkismetSetup,
175+ } = useInstallAkismet ( ) ;
29176
30177 // Handle search and filter states first
31178 const hasReadStatusFilter = ! ! readStatusFilter ;
@@ -60,7 +207,28 @@ const EmptyResponses = ( { status, isSearch, readStatusFilter }: EmptyResponsesP
60207 'Spam responses are permanently deleted after 15 days.' ,
61208 'jetpack-forms'
62209 ) ;
210+
63211 if ( status === 'spam' ) {
212+ if ( shouldShowAkismetCta ) {
213+ return (
214+ < EmptyWrapper
215+ heading = { noSpamHeading }
216+ body = { wrapperBody }
217+ actions = {
218+ < Button
219+ variant = "primary"
220+ isBusy = { isInstallingAkismet }
221+ disabled = { isInstallingAkismet || ! canPerformAkismetAction }
222+ onClick = { handleAkismetSetup }
223+ __next40pxDefaultSize
224+ >
225+ { wrapperButtonText }
226+ </ Button >
227+ }
228+ />
229+ ) ;
230+ }
231+
64232 return < EmptyWrapper heading = { noSpamHeading } body = { noSpamMessage } /> ;
65233 }
66234
0 commit comments