1- import { DotcomFeatures , updateBigSkyPlugin } from '@automattic/api-core' ;
2- import { userSettingsQuery , pluginsQuery , invalidatePlugins } from '@automattic/api-queries' ;
1+ import { HostingFeatures , updateBigSkyPlugin } from '@automattic/api-core' ;
2+ import {
3+ bigSkyPluginQuery ,
4+ invalidatePlugins ,
5+ queryClient ,
6+ siteQueryFilter ,
7+ userSettingsQuery ,
8+ } from '@automattic/api-queries' ;
39import { useQuery , useSuspenseQuery , useMutation } from '@tanstack/react-query' ;
410import { Button , __experimentalVStack as VStack } from '@wordpress/components' ;
511import { __ } from '@wordpress/i18n' ;
6- import { useState } from 'react' ;
12+ import { comment } from '@wordpress/icons' ;
13+ import { useState , useMemo , useEffect } from 'react' ;
714import Breadcrumbs from '../../../app/breadcrumbs' ;
815import { useAppContext } from '../../../app/context' ;
916import { ActionList } from '../../../components/action-list' ;
@@ -13,6 +20,7 @@ import { PageHeader } from '../../../components/page-header';
1320import PageLayout from '../../../components/page-layout' ;
1421import { SectionHeader } from '../../../components/section-header' ;
1522import SiteIcon from '../../../components/site-icon' ;
23+ import UpsellCallout from '../../../sites/hosting-feature-gated-with-callout/upsell' ;
1624import { getSiteDisplayName } from '../../../utils/site-name' ;
1725import { getSiteDisplayUrl } from '../../../utils/site-url' ;
1826import PreferencesLoginSiteDropdown from '../../preferences-primary-site/site-dropdown' ;
@@ -24,37 +32,45 @@ export default function McpAiSites() {
2432 const sitesQueryResult = useQuery (
2533 queries . sitesQuery ( { site_visibility : 'visible' , include_a8c_owned : false } )
2634 ) ;
27- const sites = ( sitesQueryResult . data as Site [ ] | undefined ) ?? [ ] ;
35+ const sites = useMemo (
36+ ( ) => ( sitesQueryResult . data as Site [ ] | undefined ) ?? [ ] ,
37+ [ sitesQueryResult . data ]
38+ ) ;
2839 const isSiteListLoading = sitesQueryResult . isLoading ;
2940
3041 const [ selectedSiteId , setSelectedSiteId ] = useState < string | null > ( null ) ;
42+ const [ pendingSiteId , setPendingSiteId ] = useState < number | null > ( null ) ;
43+ const [ upsellSite , setUpsellSite ] = useState < Site | null > ( null ) ;
3144
32- const { data : pluginsData } = useQuery ( pluginsQuery ( ) ) ;
33- const aiEnabledSiteIds = new Set (
34- Object . entries ( pluginsData ?. sites ?? { } )
35- . filter ( ( [ , plugins ] ) =>
36- plugins . some ( ( p ) => p . slug === DotcomFeatures . BIG_SKY && p . active )
37- )
38- . map ( ( [ siteId ] ) => Number ( siteId ) )
39- ) ;
45+ const aiEnabledSiteIds = useMemo ( ( ) => {
46+ return new Set < number > ( sites . filter ( ( s ) => s . big_sky_enabled ) . map ( ( s ) => s . ID ) ) ;
47+ } , [ sites ] ) ;
4048
41- const enabledSites = sites
42- . filter ( ( site : Site ) => aiEnabledSiteIds . has ( site . ID ) )
43- . map ( ( site : Site ) => ( {
44- id : site . ID ,
45- name : getSiteDisplayName ( site ) ,
46- displayUrl : getSiteDisplayUrl ( site ) ,
47- site,
48- } ) ) ;
49+ const enabledSites = useMemo (
50+ ( ) =>
51+ sites
52+ . filter ( ( site : Site ) => aiEnabledSiteIds . has ( site . ID ) )
53+ . map ( ( site : Site ) => ( {
54+ id : site . ID ,
55+ name : getSiteDisplayName ( site ) ,
56+ displayUrl : getSiteDisplayUrl ( site ) ,
57+ site,
58+ } ) ) ,
59+ [ sites , aiEnabledSiteIds ]
60+ ) ;
4961
50- const availableSitesForPicker = sites . filter (
51- ( site : Site ) => ! aiEnabledSiteIds . has ( site . ID )
62+ const availableSitesForPicker = useMemo (
63+ ( ) => sites . filter ( ( site : Site ) => ! aiEnabledSiteIds . has ( site . ID ) ) ,
64+ [ sites , aiEnabledSiteIds ]
5265 ) ;
5366
5467 const { mutate : toggleAiForSite , isPending } = useMutation ( {
5568 mutationFn : ( { siteId, enable } : { siteId : number ; enable : boolean } ) =>
5669 updateBigSkyPlugin ( siteId , { enable } ) ,
57- onSuccess : ( ) => {
70+ onSuccess : ( _ , { siteId } ) => {
71+ queryClient . invalidateQueries ( { queryKey : bigSkyPluginQuery ( siteId ) . queryKey } ) ;
72+ queryClient . invalidateQueries ( siteQueryFilter ( siteId ) ) ;
73+ queryClient . invalidateQueries ( { queryKey : [ 'sites' ] } ) ;
5874 invalidatePlugins ( ) ;
5975 } ,
6076 meta : {
@@ -65,9 +81,27 @@ export default function McpAiSites() {
6581 } ,
6682 } ) ;
6783
84+ const { data : pendingPluginStatus , isFetching : isCheckingPlan } = useQuery ( {
85+ ...bigSkyPluginQuery ( pendingSiteId ! ) ,
86+ enabled : pendingSiteId !== null ,
87+ } ) ;
88+
89+ useEffect ( ( ) => {
90+ if ( ! pendingSiteId || ! pendingPluginStatus ) {
91+ return ;
92+ }
93+ if ( pendingPluginStatus . available ) {
94+ toggleAiForSite ( { siteId : pendingSiteId , enable : true } ) ;
95+ } else {
96+ const site = sites . find ( ( s ) => s . ID === pendingSiteId ) ?? null ;
97+ setUpsellSite ( site ) ;
98+ }
99+ setPendingSiteId ( null ) ;
100+ } , [ pendingPluginStatus , pendingSiteId , sites , toggleAiForSite ] ) ;
101+
68102 const handleSitePickerSelect = ( siteIdStr : string | null | undefined ) => {
69103 if ( siteIdStr ) {
70- toggleAiForSite ( { siteId : parseInt ( siteIdStr , 10 ) , enable : true } ) ;
104+ setPendingSiteId ( parseInt ( siteIdStr , 10 ) ) ;
71105 setSelectedSiteId ( null ) ;
72106 }
73107 } ;
@@ -91,53 +125,80 @@ export default function McpAiSites() {
91125 >
92126 < ComponentViewTracker eventName = "calypso_dashboard_mcp_ai_sites_view" />
93127 < VStack spacing = { 4 } >
94- < Card >
95- < CardBody >
96- < VStack spacing = { 4 } >
97- < SectionHeader
98- level = { 3 }
99- title = { __ ( 'Add a site' ) }
100- description = { __ ( 'Search for a site to enable the AI assistant.' ) }
101- />
102- < PreferencesLoginSiteDropdown
103- sites = { availableSitesForPicker }
104- isLoading = { isSiteListLoading }
105- value = { selectedSiteId ?? '' }
106- onChange = { handleSitePickerSelect }
107- hideLabelFromVision
108- />
109- </ VStack >
110- </ CardBody >
111- </ Card >
112-
113- { enabledSites . length > 0 && (
114- < VStack spacing = { 2 } >
115- < SectionHeader
116- level = { 3 }
117- title = { __ ( 'Enabled sites' ) }
118- description = { __ ( 'These sites have the AI assistant enabled.' ) }
128+ { upsellSite ? (
129+ < >
130+ < UpsellCallout
131+ site = { upsellSite }
132+ feature = { HostingFeatures . BIG_SKY }
133+ upsellId = "ai-tools"
134+ upsellTitle = { __ ( 'Your dream site is just a prompt away' ) }
135+ upsellDescription = { __ (
136+ 'Get AI-powered assistance to help you build, edit, and redesign your site with ease.'
137+ ) }
138+ upsellIcon = { comment }
119139 />
120- < ActionList >
121- { enabledSites . map ( ( site ) => (
122- < ActionList . ActionItem
123- key = { site . id }
124- title = { site . name }
125- description = { site . displayUrl || undefined }
126- decoration = { site . site ? < SiteIcon site = { site . site } size = { 32 } /> : undefined }
127- actions = {
128- < Button
129- variant = "secondary"
130- size = "compact"
131- disabled = { isPending }
132- onClick = { ( ) => toggleAiForSite ( { siteId : site . id , enable : false } ) }
133- >
134- { __ ( 'Remove' ) }
135- </ Button >
136- }
140+ < Button
141+ variant = "tertiary"
142+ size = "compact"
143+ style = { { alignSelf : 'flex-start' } }
144+ onClick = { ( ) => setUpsellSite ( null ) }
145+ >
146+ { __ ( '← Back' ) }
147+ </ Button >
148+ </ >
149+ ) : (
150+ < >
151+ < Card >
152+ < CardBody >
153+ < VStack spacing = { 4 } >
154+ < SectionHeader
155+ level = { 3 }
156+ title = { __ ( 'Add a site' ) }
157+ description = { __ ( 'Search for a site to enable the AI assistant.' ) }
158+ />
159+ < PreferencesLoginSiteDropdown
160+ sites = { availableSitesForPicker }
161+ isLoading = { isSiteListLoading || isCheckingPlan }
162+ value = { selectedSiteId ?? '' }
163+ onChange = { handleSitePickerSelect }
164+ hideLabelFromVision
165+ />
166+ </ VStack >
167+ </ CardBody >
168+ </ Card >
169+
170+ { enabledSites . length > 0 && (
171+ < VStack spacing = { 2 } >
172+ < SectionHeader
173+ level = { 3 }
174+ title = { __ ( 'Enabled sites' ) }
175+ description = { __ ( 'These sites have the AI assistant enabled.' ) }
137176 />
138- ) ) }
139- </ ActionList >
140- </ VStack >
177+ < ActionList >
178+ { enabledSites . map ( ( site ) => (
179+ < ActionList . ActionItem
180+ key = { site . id }
181+ title = { site . name }
182+ description = { site . displayUrl || undefined }
183+ decoration = {
184+ site . site ? < SiteIcon site = { site . site } size = { 32 } /> : undefined
185+ }
186+ actions = {
187+ < Button
188+ variant = "secondary"
189+ size = "compact"
190+ disabled = { isPending }
191+ onClick = { ( ) => toggleAiForSite ( { siteId : site . id , enable : false } ) }
192+ >
193+ { __ ( 'Remove' ) }
194+ </ Button >
195+ }
196+ />
197+ ) ) }
198+ </ ActionList >
199+ </ VStack >
200+ ) }
201+ </ >
141202 ) }
142203 </ VStack >
143204 </ PageLayout >
0 commit comments