@automattic/jetpack-ai-client 0.15.0 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.16.1] - 2024-08-05
9
+ ### Changed
10
+ - AI Logo Generator: fix UI issues. [#38590]
11
+ - Fixup versions [#38612]
12
+
13
+ ### Fixed
14
+ - AI Logo Generator: fix multiple feature requests error + retry handling. [#38630]
15
+ - AI Logo Generator: fix small UI issues. [#38676]
16
+ - AI Logo Generator: fix upgrade URLs so they work on any site type. [#38598]
17
+ - AI Logo Generator: update upgrade message. [#38690]
18
+
19
+ ## [0.16.0] - 2024-07-29
20
+ ### Added
21
+ - AI Logo Generator: support placement property on the generator modal, for tracking purposes. [#38574]
22
+
23
+ ### Fixed
24
+ - AI Logo Generator: make the initial prompt update when the site name and description are fully laoded from store. [#38491]
25
+ - AI Logo Generator: provide the saved media ID on the save logo callback. [#38552]
26
+
8
27
  ## [0.15.0] - 2024-07-22
9
28
  ### Added
10
29
  - Jetpack AI: Add logo generator codebase to the ai-client package. [#38391]
@@ -358,6 +377,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
358
377
  - Updated package dependencies. [#31659]
359
378
  - Updated package dependencies. [#31785]
360
379
 
380
+ [0.16.1]: https://github.com/Automattic/jetpack-ai-client/compare/v0.16.0...v0.16.1
381
+ [0.16.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.15.0...v0.16.0
361
382
  [0.15.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.14.6...v0.15.0
362
383
  [0.14.6]: https://github.com/Automattic/jetpack-ai-client/compare/v0.14.5...v0.14.6
363
384
  [0.14.5]: https://github.com/Automattic/jetpack-ai-client/compare/v0.14.4...v0.14.5
@@ -5,6 +5,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
5
5
  import { Button } from '@wordpress/components';
6
6
  import { __ } from '@wordpress/i18n';
7
7
  export const FeatureFetchFailureScreen = ({ onCancel, onRetry }) => {
8
- const errorMessage = __('We are sorry. There was an error loading your Jetpack AI account settings. Please, try again.', 'jetpack-ai-client');
8
+ const errorMessage = __('We are sorry. There was an error loading your Jetpack AI plan data. Please, try again.', 'jetpack-ai-client');
9
9
  return (_jsxs("div", { className: "jetpack-ai-logo-generator-modal__notice-message-wrapper", children: [_jsx("div", { className: "jetpack-ai-logo-generator-modal__notice-message", children: _jsx("span", { className: "jetpack-ai-logo-generator-modal__loading-message", children: errorMessage }) }), _jsxs("div", { className: "jetpack-ai-logo-generator-modal__notice-actions", children: [_jsx(Button, { variant: "tertiary", onClick: onCancel, children: __('Cancel', 'jetpack-ai-client') }), _jsx(Button, { variant: "primary", onClick: onRetry, children: __('Try again', 'jetpack-ai-client') })] })] }));
10
10
  };
@@ -4,7 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
4
4
  */
5
5
  import { useAnalytics } from '@automattic/jetpack-shared-extension-utils';
6
6
  import { Modal, Button } from '@wordpress/components';
7
- import { useDispatch } from '@wordpress/data';
7
+ import { useDispatch, select } from '@wordpress/data';
8
8
  import { __ } from '@wordpress/i18n';
9
9
  import { external, Icon } from '@wordpress/icons';
10
10
  import clsx from 'clsx';
@@ -13,7 +13,8 @@ import { useState, useEffect, useCallback, useRef } from 'react';
13
13
  /**
14
14
  * Internal dependencies
15
15
  */
16
- import { DEFAULT_LOGO_COST, EVENT_MODAL_OPEN, EVENT_FEEDBACK, EVENT_MODAL_CLOSE, EVENT_PLACEMENT_QUICK_LINKS, EVENT_GENERATE, } from '../constants.js';
16
+ import { DEFAULT_LOGO_COST, EVENT_MODAL_OPEN, EVENT_FEEDBACK, EVENT_MODAL_CLOSE, EVENT_GENERATE, } from '../constants.js';
17
+ import { useCheckout } from '../hooks/use-checkout.js';
17
18
  import useLogoGenerator from '../hooks/use-logo-generator.js';
18
19
  import useRequestErrors from '../hooks/use-request-errors.js';
19
20
  import { isLogoHistoryEmpty, clearDeletedMedia } from '../lib/logo-storage.js';
@@ -27,22 +28,22 @@ import { UpgradeScreen } from './upgrade-screen.js';
27
28
  import { VisitSiteBanner } from './visit-site-banner.js';
28
29
  import './generator-modal.scss';
29
30
  const debug = debugFactory('jetpack-ai-calypso:generator-modal');
30
- export const GeneratorModal = ({ isOpen, onClose, siteDetails, context, }) => {
31
+ export const GeneratorModal = ({ isOpen, onClose, onApplyLogo, onReload, siteDetails, context, placement, }) => {
31
32
  const { tracks } = useAnalytics();
32
33
  const { recordEvent: recordTracksEvent } = tracks;
33
34
  const { setSiteDetails, fetchAiAssistantFeature, loadLogoHistory } = useDispatch(STORE_NAME);
35
+ const { getIsRequestingAiAssistantFeature } = select(STORE_NAME);
34
36
  const [loadingState, setLoadingState] = useState(null);
35
37
  const [initialPrompt, setInitialPrompt] = useState();
36
38
  const needsToHandleModalOpen = useRef(true);
37
39
  const requestedFeatureData = useRef(false);
38
40
  const [needsFeature, setNeedsFeature] = useState(false);
39
41
  const [needsMoreRequests, setNeedsMoreRequests] = useState(false);
40
- const [upgradeURL, setUpgradeURL] = useState('');
41
42
  const { selectedLogo, getAiAssistantFeature, generateFirstPrompt, generateLogo, setContext } = useLogoGenerator();
42
43
  const { featureFetchError, firstLogoPromptFetchError, clearErrors } = useRequestErrors();
43
44
  const siteId = siteDetails?.ID;
44
- const siteURL = siteDetails?.URL;
45
45
  const [logoAccepted, setLogoAccepted] = useState(false);
46
+ const { nextTierCheckoutURL: upgradeURL } = useCheckout();
46
47
  // First fetch the feature data so we have the most up-to-date info from the backend.
47
48
  const feature = getAiAssistantFeature();
48
49
  const generateFirstLogo = useCallback(async () => {
@@ -80,13 +81,10 @@ export const GeneratorModal = ({ isOpen, onClose, siteDetails, context, }) => {
80
81
  !hasNoNextTier &&
81
82
  !hasHistory &&
82
83
  currentLimit - currentUsage < logoCost + promptCreationCost;
83
- // If the site requires an upgrade, set the upgrade URL and show the upgrade screen immediately.
84
+ // If the site requires an upgrade, show the upgrade screen immediately.
84
85
  setNeedsFeature(!feature?.hasFeature ?? true);
85
86
  setNeedsMoreRequests(siteNeedsMoreRequests);
86
87
  if (!feature?.hasFeature || siteNeedsMoreRequests) {
87
- const siteUpgradeURL = new URL(`${location.origin}/checkout/${siteDetails?.domain}/${feature?.nextTier?.slug}`);
88
- siteUpgradeURL.searchParams.set('redirect_to', location.href);
89
- setUpgradeURL(siteUpgradeURL.toString());
90
88
  setLoadingState(null);
91
89
  return;
92
90
  }
@@ -115,9 +113,9 @@ export const GeneratorModal = ({ isOpen, onClose, siteDetails, context, }) => {
115
113
  ]);
116
114
  const handleModalOpen = useCallback(async () => {
117
115
  setContext(context);
118
- recordTracksEvent(EVENT_MODAL_OPEN, { context, placement: EVENT_PLACEMENT_QUICK_LINKS });
116
+ recordTracksEvent(EVENT_MODAL_OPEN, { context, placement });
119
117
  initializeModal();
120
- }, [setContext, context, initializeModal]);
118
+ }, [setContext, context, placement, initializeModal]);
121
119
  const closeModal = () => {
122
120
  // Reset the state when the modal is closed, so we trigger the modal initialization again when it's opened.
123
121
  needsToHandleModalOpen.current = true;
@@ -127,17 +125,11 @@ export const GeneratorModal = ({ isOpen, onClose, siteDetails, context, }) => {
127
125
  setNeedsMoreRequests(false);
128
126
  clearErrors();
129
127
  setLogoAccepted(false);
130
- recordTracksEvent(EVENT_MODAL_CLOSE, { context, placement: EVENT_PLACEMENT_QUICK_LINKS });
128
+ recordTracksEvent(EVENT_MODAL_CLOSE, { context, placement });
131
129
  };
132
- const handleApplyLogo = () => {
130
+ const handleApplyLogo = (mediaId) => {
133
131
  setLogoAccepted(true);
134
- };
135
- const handleCloseAndReload = () => {
136
- closeModal();
137
- setTimeout(() => {
138
- // Reload the page to update the logo.
139
- window.location.reload();
140
- }, 1000);
132
+ onApplyLogo?.(mediaId);
141
133
  };
142
134
  const handleFeedbackClick = () => {
143
135
  recordTracksEvent(EVENT_FEEDBACK, { context });
@@ -149,10 +141,13 @@ export const GeneratorModal = ({ isOpen, onClose, siteDetails, context, }) => {
149
141
  }
150
142
  // When the site details are set, we need to fetch the feature data.
151
143
  if (!requestedFeatureData.current) {
152
- requestedFeatureData.current = true;
153
- fetchAiAssistantFeature();
144
+ const isRequestingFeature = getIsRequestingAiAssistantFeature();
145
+ if (!isRequestingFeature) {
146
+ requestedFeatureData.current = true;
147
+ fetchAiAssistantFeature();
148
+ }
154
149
  }
155
- }, [siteId, siteDetails, setSiteDetails]);
150
+ }, [siteId, siteDetails, setSiteDetails, getIsRequestingAiAssistantFeature]);
156
151
  // Handles modal opening logic
157
152
  useEffect(() => {
158
153
  // While the modal is not open, the siteId is not set, or the feature data is not available, do nothing.
@@ -170,15 +165,18 @@ export const GeneratorModal = ({ isOpen, onClose, siteDetails, context, }) => {
170
165
  body = _jsx(FirstLoadScreen, { state: loadingState });
171
166
  }
172
167
  else if (featureFetchError || firstLogoPromptFetchError) {
173
- body = _jsx(FeatureFetchFailureScreen, { onCancel: closeModal, onRetry: initializeModal });
168
+ body = (_jsx(FeatureFetchFailureScreen, { onCancel: closeModal, onRetry: () => {
169
+ closeModal();
170
+ onReload?.();
171
+ } }));
174
172
  }
175
173
  else if (needsFeature || needsMoreRequests) {
176
174
  body = (_jsx(UpgradeScreen, { onCancel: closeModal, upgradeURL: upgradeURL, reason: needsFeature ? 'feature' : 'requests' }));
177
175
  }
178
176
  else {
179
- body = (_jsxs(_Fragment, { children: [!logoAccepted && _jsx(Prompt, { initialPrompt: initialPrompt }), _jsx(LogoPresenter, { logo: selectedLogo, onApplyLogo: handleApplyLogo, logoAccepted: logoAccepted, siteId: String(siteId) }), logoAccepted ? (_jsxs("div", { className: "jetpack-ai-logo-generator__accept", children: [_jsx(VisitSiteBanner, { siteURL: siteURL, onVisitBlankTarget: handleCloseAndReload }), _jsxs("div", { className: "jetpack-ai-logo-generator__accept-actions", children: [_jsx(Button, { variant: "link", onClick: handleCloseAndReload, children: __('Close and refresh', 'jetpack-ai-client') }), _jsx(Button, { href: siteURL, variant: "primary", children: __('Visit site', 'jetpack-ai-client') })] })] })) : (_jsxs(_Fragment, { children: [_jsx(HistoryCarousel, {}), _jsx("div", { className: "jetpack-ai-logo-generator__footer", children: _jsxs(Button, { variant: "link", className: "jetpack-ai-logo-generator__feedback-button", href: "https://jetpack.com/redirect/?source=jetpack-ai-feedback", target: "_blank", onClick: handleFeedbackClick, children: [_jsx("span", { children: __('Provide feedback', 'jetpack-ai-client') }), _jsx(Icon, { icon: external, className: "icon" })] }) })] }))] }));
177
+ body = (_jsxs(_Fragment, { children: [!logoAccepted && _jsx(Prompt, { initialPrompt: initialPrompt }), _jsx(LogoPresenter, { logo: selectedLogo, onApplyLogo: handleApplyLogo, logoAccepted: logoAccepted, siteId: String(siteId) }), logoAccepted ? (_jsxs("div", { className: "jetpack-ai-logo-generator__accept", children: [_jsx(VisitSiteBanner, {}), _jsx("div", { className: "jetpack-ai-logo-generator__accept-actions", children: _jsx(Button, { variant: "primary", onClick: closeModal, children: __('Close', 'jetpack-ai-client') }) })] })) : (_jsxs(_Fragment, { children: [_jsx(HistoryCarousel, {}), _jsx("div", { className: "jetpack-ai-logo-generator__footer", children: _jsxs(Button, { variant: "link", className: "jetpack-ai-logo-generator__feedback-button", href: "https://jetpack.com/redirect/?source=jetpack-ai-feedback", target: "_blank", onClick: handleFeedbackClick, children: [_jsx("span", { children: __('Provide feedback', 'jetpack-ai-client') }), _jsx(Icon, { icon: external, className: "icon" })] }) })] }))] }));
180
178
  }
181
- return (_jsx(_Fragment, { children: isOpen && (_jsx(Modal, { className: "jetpack-ai-logo-generator-modal", onRequestClose: logoAccepted ? handleCloseAndReload : closeModal, shouldCloseOnClickOutside: false, shouldCloseOnEsc: false, title: __('Jetpack AI Logo Generator', 'jetpack-ai-client'), children: _jsx("div", { className: clsx('jetpack-ai-logo-generator-modal__body', {
179
+ return (_jsx(_Fragment, { children: isOpen && (_jsx(Modal, { className: "jetpack-ai-logo-generator-modal", onRequestClose: closeModal, shouldCloseOnClickOutside: false, shouldCloseOnEsc: false, title: __('Jetpack AI Logo Generator', 'jetpack-ai-client'), children: _jsx("div", { className: clsx('jetpack-ai-logo-generator-modal__body', {
182
180
  'notice-modal': needsFeature || needsMoreRequests || featureFetchError || firstLogoPromptFetchError,
183
181
  }), children: body }) })) }));
184
182
  };
@@ -53,38 +53,32 @@ const SaveInLibraryButton = ({ siteId }) => {
53
53
  };
54
54
  const savingLabel = __('Saving…', 'jetpack-ai-client');
55
55
  const savedLabel = __('Saved', 'jetpack-ai-client');
56
- return !saving && !saved ? (_jsxs(Button, { className: "jetpack-ai-logo-generator-modal-presenter__action", onClick: handleClick, children: [_jsx(Icon, { icon: _jsx(MediaIcon, {}) }), _jsx("span", { className: "action-text", children: __('Save in Library', 'jetpack-ai-client') })] })) : (_jsxs("button", { className: "jetpack-ai-logo-generator-modal-presenter__action", children: [_jsx(Icon, { icon: saving ? _jsx(MediaIcon, {}) : _jsx(CheckIcon, {}) }), _jsx("span", { className: "action-text", children: saving ? savingLabel : savedLabel })] }));
56
+ return !saving && !saved ? (_jsxs(Button, { className: "jetpack-ai-logo-generator-modal-presenter__action", onClick: handleClick, children: [_jsx(Icon, { icon: _jsx(MediaIcon, {}) }), _jsx("span", { className: "action-text", children: __('Save in Library', 'jetpack-ai-client') })] })) : (_jsxs(Button, { className: "jetpack-ai-logo-generator-modal-presenter__action", children: [_jsx(Icon, { icon: saving ? _jsx(MediaIcon, {}) : _jsx(CheckIcon, {}) }), _jsx("span", { className: "action-text", children: saving ? savingLabel : savedLabel })] }));
57
57
  };
58
- const UseOnSiteButton = ({ onApplyLogo }) => {
58
+ const UseOnSiteButton = ({ onApplyLogo, }) => {
59
59
  const { tracks } = useAnalytics();
60
60
  const { recordEvent: recordTracksEvent } = tracks;
61
- const { applyLogo, isSavingLogoToLibrary, isApplyingLogo, selectedLogo, logos, selectedLogoIndex, context, } = useLogoGenerator();
61
+ const { isSavingLogoToLibrary, selectedLogo, logos, selectedLogoIndex, context } = useLogoGenerator();
62
62
  const handleClick = async () => {
63
- if (!isApplyingLogo && !isSavingLogoToLibrary) {
63
+ if (!isSavingLogoToLibrary) {
64
64
  recordTracksEvent(EVENT_USE, {
65
65
  context,
66
66
  logos_count: logos.length,
67
67
  selected_logo: selectedLogoIndex != null ? selectedLogoIndex + 1 : 0,
68
68
  });
69
- try {
70
- await applyLogo();
71
- onApplyLogo();
72
- }
73
- catch (error) {
74
- debug('Error applying logo', error);
75
- }
69
+ onApplyLogo?.(selectedLogo?.mediaId);
76
70
  }
77
71
  };
78
- return isApplyingLogo && !isSavingLogoToLibrary ? (_jsxs("button", { className: "jetpack-ai-logo-generator-modal-presenter__action", children: [_jsx(Icon, { icon: _jsx(LogoIcon, {}) }), _jsx("span", { className: "action-text", children: __('Applying logo…', 'jetpack-ai-client') })] })) : (_jsxs(Button, { className: "jetpack-ai-logo-generator-modal-presenter__action", onClick: handleClick, disabled: isSavingLogoToLibrary || !selectedLogo?.mediaId, children: [_jsx(Icon, { icon: _jsx(LogoIcon, {}) }), _jsx("span", { className: "action-text", children: __('Use on Site', 'jetpack-ai-client') })] }));
72
+ return (_jsxs(Button, { className: "jetpack-ai-logo-generator-modal-presenter__action", onClick: handleClick, disabled: isSavingLogoToLibrary || !selectedLogo?.mediaId, children: [_jsx(Icon, { icon: _jsx(LogoIcon, {}) }), _jsx("span", { className: "action-text", children: __('Use on block', 'jetpack-ai-client') })] }));
79
73
  };
80
74
  const LogoLoading = () => {
81
75
  return (_jsxs(_Fragment, { children: [_jsx(ImageLoader, { className: "jetpack-ai-logo-generator-modal-presenter__logo" }), _jsx("span", { className: "jetpack-ai-logo-generator-modal-presenter__loading-text", children: __('Generating new logo…', 'jetpack-ai-client') })] }));
82
76
  };
83
- const LogoReady = ({ siteId, logo, onApplyLogo, }) => {
77
+ const LogoReady = ({ siteId, logo, onApplyLogo }) => {
84
78
  return (_jsxs(_Fragment, { children: [_jsx("img", { src: logo.url, alt: logo.description, className: "jetpack-ai-logo-generator-modal-presenter__logo" }), _jsxs("div", { className: "jetpack-ai-logo-generator-modal-presenter__action-wrapper", children: [_jsx("span", { className: "jetpack-ai-logo-generator-modal-presenter__description", children: logo.description }), _jsxs("div", { className: "jetpack-ai-logo-generator-modal-presenter__actions", children: [_jsx(SaveInLibraryButton, { siteId: siteId }), _jsx(UseOnSiteButton, { onApplyLogo: onApplyLogo })] })] })] }));
85
79
  };
86
80
  const LogoUpdated = ({ logo }) => {
87
- return (_jsxs(_Fragment, { children: [_jsx("img", { src: logo.url, alt: logo.description, className: "jetpack-ai-logo-generator-modal-presenter__logo" }), _jsxs("div", { className: "jetpack-ai-logo-generator-modal-presenter__success-wrapper", children: [_jsx(Icon, { icon: _jsx(CheckIcon, {}) }), _jsx("span", { children: __('Your logo has been successfully updated!', 'jetpack-ai-client') })] })] }));
81
+ return (_jsxs(_Fragment, { children: [_jsx("img", { src: logo.url, alt: logo.description, className: "jetpack-ai-logo-generator-modal-presenter__logo" }), _jsxs("div", { className: "jetpack-ai-logo-generator-modal-presenter__success-wrapper", children: [_jsx(Icon, { icon: _jsx(CheckIcon, {}) }), _jsx("span", { children: __('Your new logo was set to the block!', 'jetpack-ai-client') })] })] }));
88
82
  };
89
83
  export const LogoPresenter = ({ logo = null, loading = false, onApplyLogo, logoAccepted = false, siteId, }) => {
90
84
  const { isRequestingImage } = useLogoGenerator();
@@ -13,12 +13,12 @@ import useLogoGenerator from '../hooks/use-logo-generator.js';
13
13
  export const UpgradeScreen = ({ onCancel, upgradeURL, reason }) => {
14
14
  const { tracks } = useAnalytics();
15
15
  const { recordEvent: recordTracksEvent } = tracks;
16
- const upgradeMessageFeature = __('Upgrade your Jetpack AI for access to exclusive features, including logo generation. This upgrade will also increase the amount of requests you can use in all AI-powered features.', 'jetpack-ai-client');
16
+ const upgradeMessageFeature = __('The logo generator requires a paid Jetpack AI plan. Upgrade your plan to access exclusive features, including logo generation. The upgrade will also increase the amount of requests you can use in all AI-powered features.', 'jetpack-ai-client');
17
17
  const upgradeMessageRequests = __('Not enough requests left to generate a logo. Upgrade your Jetpack AI to increase the amount of requests you can use in all AI-powered features.', 'jetpack-ai-client');
18
18
  const { context } = useLogoGenerator();
19
19
  const handleUpgradeClick = () => {
20
20
  recordTracksEvent(EVENT_UPGRADE, { context, placement: EVENT_PLACEMENT_FREE_USER_SCREEN });
21
21
  onCancel();
22
22
  };
23
- return (_jsxs("div", { className: "jetpack-ai-logo-generator-modal__notice-message-wrapper", children: [_jsxs("div", { className: "jetpack-ai-logo-generator-modal__notice-message", children: [_jsx("span", { className: "jetpack-ai-logo-generator-modal__loading-message", children: reason === 'feature' ? upgradeMessageFeature : upgradeMessageRequests }), "\u00A0", _jsx(Button, { variant: "link", href: "https://jetpack.com/ai/", target: "_blank", children: __('Learn more', 'jetpack-ai-client') })] }), _jsxs("div", { className: "jetpack-ai-logo-generator-modal__notice-actions", children: [_jsx(Button, { variant: "tertiary", onClick: onCancel, children: __('Cancel', 'jetpack-ai-client') }), _jsx(Button, { variant: "primary", href: upgradeURL, target: "_blank", onClick: handleUpgradeClick, children: __('Upgrade', 'jetpack-ai-client') })] })] }));
23
+ return (_jsxs("div", { className: "jetpack-ai-logo-generator-modal__notice-message-wrapper", children: [_jsxs("div", { className: "jetpack-ai-logo-generator-modal__notice-message", children: [_jsx("span", { className: "jetpack-ai-logo-generator-modal__loading-message", children: reason === 'feature' ? upgradeMessageFeature : upgradeMessageRequests }), "\u00A0", _jsx(Button, { variant: "link", href: "https://jetpack.com/ai/", target: "_blank", children: __('Learn more about Jetpack AI.', 'jetpack-ai-client') })] }), _jsxs("div", { className: "jetpack-ai-logo-generator-modal__notice-actions", children: [_jsx(Button, { variant: "tertiary", onClick: onCancel, children: __('Cancel', 'jetpack-ai-client') }), _jsx(Button, { variant: "primary", href: upgradeURL, target: "_blank", onClick: handleUpgradeClick, children: __('Upgrade', 'jetpack-ai-client') })] })] }));
24
24
  };
@@ -5,6 +5,5 @@ import './visit-site-banner.scss';
5
5
  import type React from 'react';
6
6
  export declare const VisitSiteBanner: React.FC<{
7
7
  className?: string;
8
- siteURL?: string;
9
- onVisitBlankTarget: () => void;
8
+ onVisitBlankTarget?: () => void;
10
9
  }>;
@@ -11,6 +11,6 @@ import clsx from 'clsx';
11
11
  */
12
12
  import jetpackLogo from '../assets/images/jetpack-logo.svg';
13
13
  import './visit-site-banner.scss';
14
- export const VisitSiteBanner = ({ className = null, siteURL = '#', onVisitBlankTarget }) => {
15
- return (_jsxs("div", { className: clsx('jetpack-ai-logo-generator-modal-visit-site-banner', className), children: [_jsx("div", { className: "jetpack-ai-logo-generator-modal-visit-site-banner__jetpack-logo", children: _jsx("img", { src: jetpackLogo, alt: "Jetpack" }) }), _jsxs("div", { className: "jetpack-ai-logo-generator-modal-visit-site-banner__content", children: [_jsx("strong", { children: __('Do you want to know all the amazing things you can do with Jetpack AI?', 'jetpack-ai-client') }), _jsx("span", { children: __('Generate and tweak content, create forms, get feedback and much more.', 'jetpack-ai-client') }), _jsx("div", { children: _jsxs(Button, { variant: "link", href: siteURL, target: "_blank", onClick: onVisitBlankTarget, children: [__('Visit website', 'jetpack-ai-client'), _jsx(Icon, { icon: external, size: 20 })] }) })] })] }));
14
+ export const VisitSiteBanner = ({ className = null, onVisitBlankTarget }) => {
15
+ return (_jsxs("div", { className: clsx('jetpack-ai-logo-generator-modal-visit-site-banner', className), children: [_jsx("div", { className: "jetpack-ai-logo-generator-modal-visit-site-banner__jetpack-logo", children: _jsx("img", { src: jetpackLogo, alt: "Jetpack" }) }), _jsxs("div", { className: "jetpack-ai-logo-generator-modal-visit-site-banner__content", children: [_jsx("strong", { children: __('Do you want to know all the amazing things you can do with Jetpack AI?', 'jetpack-ai-client') }), _jsx("span", { children: __('Generate and tweak content, create forms, get feedback and much more.', 'jetpack-ai-client') }), _jsx("div", { children: _jsxs(Button, { variant: "link", href: "https://jetpack.com/redirect/?source=logo_generator_learn_more_about_jetpack_ai", target: "_blank", onClick: onVisitBlankTarget ? onVisitBlankTarget : null, children: [__('Learn more about Jetpack AI', 'jetpack-ai-client'), _jsx(Icon, { icon: external, size: 20 })] }) })] })] }));
16
16
  };
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
+ import { isAtomicSite, isSimpleSite, getSiteFragment, } from '@automattic/jetpack-shared-extension-utils';
4
5
  import { useSelect } from '@wordpress/data';
5
6
  import debugFactory from 'debug';
6
7
  /**
@@ -9,18 +10,27 @@ import debugFactory from 'debug';
9
10
  import { STORE_NAME } from '../store/index.js';
10
11
  const debug = debugFactory('ai-client:logo-generator:use-checkout');
11
12
  export const useCheckout = () => {
12
- const { nextTier, siteDetails } = useSelect(select => {
13
+ const { nextTier } = useSelect(select => {
13
14
  const selectors = select(STORE_NAME);
14
15
  return {
15
16
  nextTier: selectors.getAiAssistantFeature().nextTier,
16
- siteDetails: selectors.getSiteDetails(),
17
17
  };
18
18
  }, []);
19
- const upgradeURL = new URL(`${location.origin}/checkout/${siteDetails?.domain}/${nextTier?.slug}`);
20
- upgradeURL.searchParams.set('redirect_to', location.href);
21
- debug('Next tier checkout URL: ', upgradeURL.toString());
19
+ /**
20
+ * Use the Jetpack redirect URL to open the checkout page
21
+ */
22
+ const wpcomCheckoutUrl = new URL(`https://jetpack.com/redirect/`);
23
+ wpcomCheckoutUrl.searchParams.set('source', 'jetpack-ai-yearly-tier-upgrade-nudge');
24
+ wpcomCheckoutUrl.searchParams.set('site', getSiteFragment());
25
+ wpcomCheckoutUrl.searchParams.set('path', `jetpack_ai_yearly:-q-${nextTier?.limit}`);
26
+ /**
27
+ * Open the product interstitial page
28
+ */
29
+ const jetpackCheckoutUrl = `${window?.Jetpack_Editor_Initial_State?.adminUrl}admin.php?redirect_to_referrer=1&page=my-jetpack#/add-jetpack-ai`;
30
+ const nextTierCheckoutURL = isAtomicSite() || isSimpleSite() ? wpcomCheckoutUrl.toString() : jetpackCheckoutUrl;
31
+ debug('Next tier checkout URL: ', nextTierCheckoutURL);
22
32
  return {
23
- nextTierCheckoutURL: upgradeURL.toString(),
33
+ nextTierCheckoutURL,
24
34
  hasNextTier: !!nextTier,
25
35
  };
26
36
  };
@@ -78,7 +78,7 @@ Site description: ${description}`;
78
78
  setFirstLogoPromptFetchError(error);
79
79
  throw error;
80
80
  }
81
- }, [setFirstLogoPromptFetchError, increaseAiAssistantRequestsCount]);
81
+ }, [setFirstLogoPromptFetchError, increaseAiAssistantRequestsCount, name, description]);
82
82
  const enhancePrompt = async function ({ prompt }) {
83
83
  setEnhancePromptFetchError(null);
84
84
  increaseAiAssistantRequestsCount();
@@ -119,5 +119,7 @@ export async function clearDeletedMedia(siteId) {
119
119
  .filter(({ exists }) => !exists)
120
120
  .forEach(({ mediaId }) => removeLogo({ siteId, mediaId }));
121
121
  }
122
- catch (error) { } // Assume that the media exists if there was a network error and do nothing to avoid data loss.
122
+ catch (error) {
123
+ // Assume that the media exists if there was a network error and do nothing to avoid data loss.
124
+ }
123
125
  }
@@ -7,7 +7,6 @@ import apiFetch from '../../api-fetch/index.js';
7
7
  */
8
8
  const MAX_CONCURRENT_REQUESTS = 5;
9
9
  let concurrentCounter = 0;
10
- let lastCallTimestamp = null;
11
10
  /**
12
11
  * Concurrency-limited request to wpcom-proxy-request.
13
12
  * @param { object } params - The request params, as expected by apiFetch.
@@ -20,13 +19,6 @@ export default async function wpcomLimitedRequest(params) {
20
19
  concurrentCounter -= 1;
21
20
  throw new Error('Too many requests');
22
21
  }
23
- const now = Date.now();
24
- // Check if the last call was made less than 100 milliseconds ago
25
- if (lastCallTimestamp && now - lastCallTimestamp < 100) {
26
- concurrentCounter -= 1;
27
- throw new Error('Too many requests');
28
- }
29
- lastCallTimestamp = now; // Update the timestamp
30
22
  return apiFetch(params).finally(() => {
31
23
  concurrentCounter -= 1;
32
24
  });
@@ -13,12 +13,15 @@ export interface GeneratorModalProps {
13
13
  siteDetails?: SiteDetails;
14
14
  isOpen: boolean;
15
15
  onClose: () => void;
16
+ onApplyLogo: (mediaId: number) => void;
17
+ onReload: () => void;
16
18
  context: string;
19
+ placement: string;
17
20
  }
18
21
  export interface LogoPresenterProps {
19
22
  logo?: Logo;
20
23
  loading?: boolean;
21
- onApplyLogo: () => void;
24
+ onApplyLogo: (mediaId: number) => void;
22
25
  logoAccepted?: boolean;
23
26
  siteId: string | number;
24
27
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@automattic/jetpack-ai-client",
4
- "version": "0.15.0",
4
+ "version": "0.16.1",
5
5
  "description": "A JS client for consuming Jetpack AI services",
6
6
  "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/ai-client/#readme",
7
7
  "bugs": {
@@ -44,7 +44,7 @@
44
44
  "dependencies": {
45
45
  "@automattic/jetpack-base-styles": "^0.6.28",
46
46
  "@automattic/jetpack-connection": "^0.34.1",
47
- "@automattic/jetpack-shared-extension-utils": "^0.14.19",
47
+ "@automattic/jetpack-shared-extension-utils": "^0.15.1",
48
48
  "@microsoft/fetch-event-source": "2.0.1",
49
49
  "@types/react": "18.3.1",
50
50
  "@types/wordpress__block-editor": "11.5.14",
@@ -13,7 +13,7 @@ export const FeatureFetchFailureScreen: React.FC< {
13
13
  onRetry: () => void;
14
14
  } > = ( { onCancel, onRetry } ) => {
15
15
  const errorMessage = __(
16
- 'We are sorry. There was an error loading your Jetpack AI account settings. Please, try again.',
16
+ 'We are sorry. There was an error loading your Jetpack AI plan data. Please, try again.',
17
17
  'jetpack-ai-client'
18
18
  );
19
19
 
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { useAnalytics } from '@automattic/jetpack-shared-extension-utils';
5
5
  import { Modal, Button } from '@wordpress/components';
6
- import { useDispatch } from '@wordpress/data';
6
+ import { useDispatch, select } from '@wordpress/data';
7
7
  import { __ } from '@wordpress/i18n';
8
8
  import { external, Icon } from '@wordpress/icons';
9
9
  import clsx from 'clsx';
@@ -17,9 +17,9 @@ import {
17
17
  EVENT_MODAL_OPEN,
18
18
  EVENT_FEEDBACK,
19
19
  EVENT_MODAL_CLOSE,
20
- EVENT_PLACEMENT_QUICK_LINKS,
21
20
  EVENT_GENERATE,
22
21
  } from '../constants.js';
22
+ import { useCheckout } from '../hooks/use-checkout.js';
23
23
  import useLogoGenerator from '../hooks/use-logo-generator.js';
24
24
  import useRequestErrors from '../hooks/use-request-errors.js';
25
25
  import { isLogoHistoryEmpty, clearDeletedMedia } from '../lib/logo-storage.js';
@@ -43,12 +43,16 @@ const debug = debugFactory( 'jetpack-ai-calypso:generator-modal' );
43
43
  export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
44
44
  isOpen,
45
45
  onClose,
46
+ onApplyLogo,
47
+ onReload,
46
48
  siteDetails,
47
49
  context,
50
+ placement,
48
51
  } ) => {
49
52
  const { tracks } = useAnalytics();
50
53
  const { recordEvent: recordTracksEvent } = tracks;
51
54
  const { setSiteDetails, fetchAiAssistantFeature, loadLogoHistory } = useDispatch( STORE_NAME );
55
+ const { getIsRequestingAiAssistantFeature } = select( STORE_NAME );
52
56
  const [ loadingState, setLoadingState ] = useState<
53
57
  'loadingFeature' | 'analyzing' | 'generating' | null
54
58
  >( null );
@@ -57,13 +61,12 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
57
61
  const requestedFeatureData = useRef< boolean >( false );
58
62
  const [ needsFeature, setNeedsFeature ] = useState( false );
59
63
  const [ needsMoreRequests, setNeedsMoreRequests ] = useState( false );
60
- const [ upgradeURL, setUpgradeURL ] = useState( '' );
61
64
  const { selectedLogo, getAiAssistantFeature, generateFirstPrompt, generateLogo, setContext } =
62
65
  useLogoGenerator();
63
66
  const { featureFetchError, firstLogoPromptFetchError, clearErrors } = useRequestErrors();
64
67
  const siteId = siteDetails?.ID;
65
- const siteURL = siteDetails?.URL;
66
68
  const [ logoAccepted, setLogoAccepted ] = useState( false );
69
+ const { nextTierCheckoutURL: upgradeURL } = useCheckout();
67
70
 
68
71
  // First fetch the feature data so we have the most up-to-date info from the backend.
69
72
  const feature = getAiAssistantFeature();
@@ -107,16 +110,10 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
107
110
  ! hasHistory &&
108
111
  currentLimit - currentUsage < logoCost + promptCreationCost;
109
112
 
110
- // If the site requires an upgrade, set the upgrade URL and show the upgrade screen immediately.
113
+ // If the site requires an upgrade, show the upgrade screen immediately.
111
114
  setNeedsFeature( ! feature?.hasFeature ?? true );
112
115
  setNeedsMoreRequests( siteNeedsMoreRequests );
113
-
114
116
  if ( ! feature?.hasFeature || siteNeedsMoreRequests ) {
115
- const siteUpgradeURL = new URL(
116
- `${ location.origin }/checkout/${ siteDetails?.domain }/${ feature?.nextTier?.slug }`
117
- );
118
- siteUpgradeURL.searchParams.set( 'redirect_to', location.href );
119
- setUpgradeURL( siteUpgradeURL.toString() );
120
117
  setLoadingState( null );
121
118
  return;
122
119
  }
@@ -148,10 +145,10 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
148
145
 
149
146
  const handleModalOpen = useCallback( async () => {
150
147
  setContext( context );
151
- recordTracksEvent( EVENT_MODAL_OPEN, { context, placement: EVENT_PLACEMENT_QUICK_LINKS } );
148
+ recordTracksEvent( EVENT_MODAL_OPEN, { context, placement } );
152
149
 
153
150
  initializeModal();
154
- }, [ setContext, context, initializeModal ] );
151
+ }, [ setContext, context, placement, initializeModal ] );
155
152
 
156
153
  const closeModal = () => {
157
154
  // Reset the state when the modal is closed, so we trigger the modal initialization again when it's opened.
@@ -162,20 +159,12 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
162
159
  setNeedsMoreRequests( false );
163
160
  clearErrors();
164
161
  setLogoAccepted( false );
165
- recordTracksEvent( EVENT_MODAL_CLOSE, { context, placement: EVENT_PLACEMENT_QUICK_LINKS } );
162
+ recordTracksEvent( EVENT_MODAL_CLOSE, { context, placement } );
166
163
  };
167
164
 
168
- const handleApplyLogo = () => {
165
+ const handleApplyLogo = ( mediaId: number ) => {
169
166
  setLogoAccepted( true );
170
- };
171
-
172
- const handleCloseAndReload = () => {
173
- closeModal();
174
-
175
- setTimeout( () => {
176
- // Reload the page to update the logo.
177
- window.location.reload();
178
- }, 1000 );
167
+ onApplyLogo?.( mediaId );
179
168
  };
180
169
 
181
170
  const handleFeedbackClick = () => {
@@ -190,10 +179,13 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
190
179
 
191
180
  // When the site details are set, we need to fetch the feature data.
192
181
  if ( ! requestedFeatureData.current ) {
193
- requestedFeatureData.current = true;
194
- fetchAiAssistantFeature();
182
+ const isRequestingFeature = getIsRequestingAiAssistantFeature();
183
+ if ( ! isRequestingFeature ) {
184
+ requestedFeatureData.current = true;
185
+ fetchAiAssistantFeature();
186
+ }
195
187
  }
196
- }, [ siteId, siteDetails, setSiteDetails ] );
188
+ }, [ siteId, siteDetails, setSiteDetails, getIsRequestingAiAssistantFeature ] );
197
189
 
198
190
  // Handles modal opening logic
199
191
  useEffect( () => {
@@ -214,7 +206,15 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
214
206
  if ( loadingState ) {
215
207
  body = <FirstLoadScreen state={ loadingState } />;
216
208
  } else if ( featureFetchError || firstLogoPromptFetchError ) {
217
- body = <FeatureFetchFailureScreen onCancel={ closeModal } onRetry={ initializeModal } />;
209
+ body = (
210
+ <FeatureFetchFailureScreen
211
+ onCancel={ closeModal }
212
+ onRetry={ () => {
213
+ closeModal();
214
+ onReload?.();
215
+ } }
216
+ />
217
+ );
218
218
  } else if ( needsFeature || needsMoreRequests ) {
219
219
  body = (
220
220
  <UpgradeScreen
@@ -235,13 +235,10 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
235
235
  />
236
236
  { logoAccepted ? (
237
237
  <div className="jetpack-ai-logo-generator__accept">
238
- <VisitSiteBanner siteURL={ siteURL } onVisitBlankTarget={ handleCloseAndReload } />
238
+ <VisitSiteBanner />
239
239
  <div className="jetpack-ai-logo-generator__accept-actions">
240
- <Button variant="link" onClick={ handleCloseAndReload }>
241
- { __( 'Close and refresh', 'jetpack-ai-client' ) }
242
- </Button>
243
- <Button href={ siteURL } variant="primary">
244
- { __( 'Visit site', 'jetpack-ai-client' ) }
240
+ <Button variant="primary" onClick={ closeModal }>
241
+ { __( 'Close', 'jetpack-ai-client' ) }
245
242
  </Button>
246
243
  </div>
247
244
  </div>
@@ -271,7 +268,7 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
271
268
  { isOpen && (
272
269
  <Modal
273
270
  className="jetpack-ai-logo-generator-modal"
274
- onRequestClose={ logoAccepted ? handleCloseAndReload : closeModal }
271
+ onRequestClose={ closeModal }
275
272
  shouldCloseOnClickOutside={ false }
276
273
  shouldCloseOnEsc={ false }
277
274
  title={ __( 'Jetpack AI Logo Generator', 'jetpack-ai-client' ) }
@@ -79,56 +79,41 @@ const SaveInLibraryButton: React.FC< { siteId: string } > = ( { siteId } ) => {
79
79
  <span className="action-text">{ __( 'Save in Library', 'jetpack-ai-client' ) }</span>
80
80
  </Button>
81
81
  ) : (
82
- <button className="jetpack-ai-logo-generator-modal-presenter__action">
82
+ <Button className="jetpack-ai-logo-generator-modal-presenter__action">
83
83
  <Icon icon={ saving ? <MediaIcon /> : <CheckIcon /> } />
84
84
  <span className="action-text">{ saving ? savingLabel : savedLabel }</span>
85
- </button>
85
+ </Button>
86
86
  );
87
87
  };
88
88
 
89
- const UseOnSiteButton: React.FC< { onApplyLogo: () => void } > = ( { onApplyLogo } ) => {
89
+ const UseOnSiteButton: React.FC< { onApplyLogo: ( mediaId: number ) => void } > = ( {
90
+ onApplyLogo,
91
+ } ) => {
90
92
  const { tracks } = useAnalytics();
91
93
  const { recordEvent: recordTracksEvent } = tracks;
92
- const {
93
- applyLogo,
94
- isSavingLogoToLibrary,
95
- isApplyingLogo,
96
- selectedLogo,
97
- logos,
98
- selectedLogoIndex,
99
- context,
100
- } = useLogoGenerator();
94
+ const { isSavingLogoToLibrary, selectedLogo, logos, selectedLogoIndex, context } =
95
+ useLogoGenerator();
101
96
 
102
97
  const handleClick = async () => {
103
- if ( ! isApplyingLogo && ! isSavingLogoToLibrary ) {
98
+ if ( ! isSavingLogoToLibrary ) {
104
99
  recordTracksEvent( EVENT_USE, {
105
100
  context,
106
101
  logos_count: logos.length,
107
102
  selected_logo: selectedLogoIndex != null ? selectedLogoIndex + 1 : 0,
108
103
  } );
109
104
 
110
- try {
111
- await applyLogo();
112
- onApplyLogo();
113
- } catch ( error ) {
114
- debug( 'Error applying logo', error );
115
- }
105
+ onApplyLogo?.( selectedLogo?.mediaId );
116
106
  }
117
107
  };
118
108
 
119
- return isApplyingLogo && ! isSavingLogoToLibrary ? (
120
- <button className="jetpack-ai-logo-generator-modal-presenter__action">
121
- <Icon icon={ <LogoIcon /> } />
122
- <span className="action-text">{ __( 'Applying logo…', 'jetpack-ai-client' ) }</span>
123
- </button>
124
- ) : (
109
+ return (
125
110
  <Button
126
111
  className="jetpack-ai-logo-generator-modal-presenter__action"
127
112
  onClick={ handleClick }
128
113
  disabled={ isSavingLogoToLibrary || ! selectedLogo?.mediaId }
129
114
  >
130
115
  <Icon icon={ <LogoIcon /> } />
131
- <span className="action-text">{ __( 'Use on Site', 'jetpack-ai-client' ) }</span>
116
+ <span className="action-text">{ __( 'Use on block', 'jetpack-ai-client' ) }</span>
132
117
  </Button>
133
118
  );
134
119
  };
@@ -144,11 +129,11 @@ const LogoLoading: React.FC = () => {
144
129
  );
145
130
  };
146
131
 
147
- const LogoReady: React.FC< { siteId: string; logo: Logo; onApplyLogo: () => void } > = ( {
148
- siteId,
149
- logo,
150
- onApplyLogo,
151
- } ) => {
132
+ const LogoReady: React.FC< {
133
+ siteId: string;
134
+ logo: Logo;
135
+ onApplyLogo: ( mediaId: number ) => void;
136
+ } > = ( { siteId, logo, onApplyLogo } ) => {
152
137
  return (
153
138
  <>
154
139
  <img
@@ -179,7 +164,7 @@ const LogoUpdated: React.FC< { logo: Logo } > = ( { logo } ) => {
179
164
  />
180
165
  <div className="jetpack-ai-logo-generator-modal-presenter__success-wrapper">
181
166
  <Icon icon={ <CheckIcon /> } />
182
- <span>{ __( 'Your logo has been successfully updated!', 'jetpack-ai-client' ) }</span>
167
+ <span>{ __( 'Your new logo was set to the block!', 'jetpack-ai-client' ) }</span>
183
168
  </div>
184
169
  </>
185
170
  );
@@ -22,7 +22,7 @@ export const UpgradeScreen: React.FC< {
22
22
  const { tracks } = useAnalytics();
23
23
  const { recordEvent: recordTracksEvent } = tracks;
24
24
  const upgradeMessageFeature = __(
25
- 'Upgrade your Jetpack AI for access to exclusive features, including logo generation. This upgrade will also increase the amount of requests you can use in all AI-powered features.',
25
+ 'The logo generator requires a paid Jetpack AI plan. Upgrade your plan to access exclusive features, including logo generation. The upgrade will also increase the amount of requests you can use in all AI-powered features.',
26
26
  'jetpack-ai-client'
27
27
  );
28
28
 
@@ -46,7 +46,7 @@ export const UpgradeScreen: React.FC< {
46
46
  </span>
47
47
  &nbsp;
48
48
  <Button variant="link" href="https://jetpack.com/ai/" target="_blank">
49
- { __( 'Learn more', 'jetpack-ai-client' ) }
49
+ { __( 'Learn more about Jetpack AI.', 'jetpack-ai-client' ) }
50
50
  </Button>
51
51
  </div>
52
52
  <div className="jetpack-ai-logo-generator-modal__notice-actions">
@@ -17,9 +17,8 @@ import type React from 'react';
17
17
 
18
18
  export const VisitSiteBanner: React.FC< {
19
19
  className?: string;
20
- siteURL?: string;
21
- onVisitBlankTarget: () => void;
22
- } > = ( { className = null, siteURL = '#', onVisitBlankTarget } ) => {
20
+ onVisitBlankTarget?: () => void;
21
+ } > = ( { className = null, onVisitBlankTarget } ) => {
23
22
  return (
24
23
  <div className={ clsx( 'jetpack-ai-logo-generator-modal-visit-site-banner', className ) }>
25
24
  <div className="jetpack-ai-logo-generator-modal-visit-site-banner__jetpack-logo">
@@ -39,8 +38,13 @@ export const VisitSiteBanner: React.FC< {
39
38
  ) }
40
39
  </span>
41
40
  <div>
42
- <Button variant="link" href={ siteURL } target="_blank" onClick={ onVisitBlankTarget }>
43
- { __( 'Visit website', 'jetpack-ai-client' ) }
41
+ <Button
42
+ variant="link"
43
+ href="https://jetpack.com/redirect/?source=logo_generator_learn_more_about_jetpack_ai"
44
+ target="_blank"
45
+ onClick={ onVisitBlankTarget ? onVisitBlankTarget : null }
46
+ >
47
+ { __( 'Learn more about Jetpack AI', 'jetpack-ai-client' ) }
44
48
  <Icon icon={ external } size={ 20 } />
45
49
  </Button>
46
50
  </div>
@@ -1,6 +1,11 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
+ import {
5
+ isAtomicSite,
6
+ isSimpleSite,
7
+ getSiteFragment,
8
+ } from '@automattic/jetpack-shared-extension-utils';
4
9
  import { useSelect } from '@wordpress/data';
5
10
  import debugFactory from 'debug';
6
11
  /**
@@ -15,23 +20,33 @@ import type { Selectors } from '../store/types.js';
15
20
  const debug = debugFactory( 'ai-client:logo-generator:use-checkout' );
16
21
 
17
22
  export const useCheckout = () => {
18
- const { nextTier, siteDetails } = useSelect( select => {
23
+ const { nextTier } = useSelect( select => {
19
24
  const selectors: Selectors = select( STORE_NAME );
20
25
  return {
21
26
  nextTier: selectors.getAiAssistantFeature().nextTier,
22
- siteDetails: selectors.getSiteDetails(),
23
27
  };
24
28
  }, [] );
25
29
 
26
- const upgradeURL = new URL(
27
- `${ location.origin }/checkout/${ siteDetails?.domain }/${ nextTier?.slug }`
28
- );
29
- upgradeURL.searchParams.set( 'redirect_to', location.href );
30
+ /**
31
+ * Use the Jetpack redirect URL to open the checkout page
32
+ */
33
+ const wpcomCheckoutUrl = new URL( `https://jetpack.com/redirect/` );
34
+ wpcomCheckoutUrl.searchParams.set( 'source', 'jetpack-ai-yearly-tier-upgrade-nudge' );
35
+ wpcomCheckoutUrl.searchParams.set( 'site', getSiteFragment() as string );
36
+ wpcomCheckoutUrl.searchParams.set( 'path', `jetpack_ai_yearly:-q-${ nextTier?.limit }` );
30
37
 
31
- debug( 'Next tier checkout URL: ', upgradeURL.toString() );
38
+ /**
39
+ * Open the product interstitial page
40
+ */
41
+ const jetpackCheckoutUrl = `${ window?.Jetpack_Editor_Initial_State?.adminUrl }admin.php?redirect_to_referrer=1&page=my-jetpack#/add-jetpack-ai`;
42
+
43
+ const nextTierCheckoutURL =
44
+ isAtomicSite() || isSimpleSite() ? wpcomCheckoutUrl.toString() : jetpackCheckoutUrl;
45
+
46
+ debug( 'Next tier checkout URL: ', nextTierCheckoutURL );
32
47
 
33
48
  return {
34
- nextTierCheckoutURL: upgradeURL.toString(),
49
+ nextTierCheckoutURL,
35
50
  hasNextTier: !! nextTier,
36
51
  };
37
52
  };
@@ -128,7 +128,7 @@ Site description: ${ description }`;
128
128
  throw error;
129
129
  }
130
130
  },
131
- [ setFirstLogoPromptFetchError, increaseAiAssistantRequestsCount ]
131
+ [ setFirstLogoPromptFetchError, increaseAiAssistantRequestsCount, name, description ]
132
132
  );
133
133
 
134
134
  const enhancePrompt = async function ( { prompt }: { prompt: string } ): Promise< string > {
@@ -162,5 +162,7 @@ export async function clearDeletedMedia( siteId: string ) {
162
162
  responses
163
163
  .filter( ( { exists } ) => ! exists )
164
164
  .forEach( ( { mediaId } ) => removeLogo( { siteId, mediaId } ) );
165
- } catch ( error ) {} // Assume that the media exists if there was a network error and do nothing to avoid data loss.
165
+ } catch ( error ) {
166
+ // Assume that the media exists if there was a network error and do nothing to avoid data loss.
167
+ }
166
168
  }
@@ -9,7 +9,6 @@ import apiFetch from '../../api-fetch/index.js';
9
9
  const MAX_CONCURRENT_REQUESTS = 5;
10
10
 
11
11
  let concurrentCounter = 0;
12
- let lastCallTimestamp: number | null = null;
13
12
 
14
13
  /**
15
14
  * Concurrency-limited request to wpcom-proxy-request.
@@ -25,16 +24,6 @@ export default async function wpcomLimitedRequest< T >( params: object ): Promis
25
24
  throw new Error( 'Too many requests' );
26
25
  }
27
26
 
28
- const now = Date.now();
29
-
30
- // Check if the last call was made less than 100 milliseconds ago
31
- if ( lastCallTimestamp && now - lastCallTimestamp < 100 ) {
32
- concurrentCounter -= 1;
33
- throw new Error( 'Too many requests' );
34
- }
35
-
36
- lastCallTimestamp = now; // Update the timestamp
37
-
38
27
  return apiFetch< T >( params ).finally( () => {
39
28
  concurrentCounter -= 1;
40
29
  } );
@@ -15,13 +15,16 @@ export interface GeneratorModalProps {
15
15
  siteDetails?: SiteDetails;
16
16
  isOpen: boolean;
17
17
  onClose: () => void;
18
+ onApplyLogo: ( mediaId: number ) => void;
19
+ onReload: () => void;
18
20
  context: string;
21
+ placement: string;
19
22
  }
20
23
 
21
24
  export interface LogoPresenterProps {
22
25
  logo?: Logo;
23
26
  loading?: boolean;
24
- onApplyLogo: () => void;
27
+ onApplyLogo: ( mediaId: number ) => void;
25
28
  logoAccepted?: boolean;
26
29
  siteId: string | number;
27
30
  }