@automattic/jetpack-ai-client 0.16.0 → 0.16.2

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,24 @@ 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.2] - 2024-08-19
9
+ ### Changed
10
+ - Update dependencies. [#38861] [#38662] [#38665] [#38893]
11
+
12
+ ### Fixed
13
+ - Lossless image optimization for images (should improve performance with no visible changes). [#38750]
14
+
15
+ ## [0.16.1] - 2024-08-05
16
+ ### Changed
17
+ - AI Logo Generator: fix UI issues. [#38590]
18
+ - Fixup versions [#38612]
19
+
20
+ ### Fixed
21
+ - AI Logo Generator: fix multiple feature requests error + retry handling. [#38630]
22
+ - AI Logo Generator: fix small UI issues. [#38676]
23
+ - AI Logo Generator: fix upgrade URLs so they work on any site type. [#38598]
24
+ - AI Logo Generator: update upgrade message. [#38690]
25
+
8
26
  ## [0.16.0] - 2024-07-29
9
27
  ### Added
10
28
  - AI Logo Generator: support placement property on the generator modal, for tracking purposes. [#38574]
@@ -366,6 +384,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
366
384
  - Updated package dependencies. [#31659]
367
385
  - Updated package dependencies. [#31785]
368
386
 
387
+ [0.16.2]: https://github.com/Automattic/jetpack-ai-client/compare/v0.16.1...v0.16.2
388
+ [0.16.1]: https://github.com/Automattic/jetpack-ai-client/compare/v0.16.0...v0.16.1
369
389
  [0.16.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.15.0...v0.16.0
370
390
  [0.15.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.14.6...v0.15.0
371
391
  [0.14.6]: https://github.com/Automattic/jetpack-ai-client/compare/v0.14.5...v0.14.6
@@ -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';
@@ -14,6 +14,7 @@ import { useState, useEffect, useCallback, useRef } from 'react';
14
14
  * Internal dependencies
15
15
  */
16
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,21 +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, onApplyLogo, siteDetails, context, placement, }) => {
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
45
  const [logoAccepted, setLogoAccepted] = useState(false);
46
+ const { nextTierCheckoutURL: upgradeURL } = useCheckout();
45
47
  // First fetch the feature data so we have the most up-to-date info from the backend.
46
48
  const feature = getAiAssistantFeature();
47
49
  const generateFirstLogo = useCallback(async () => {
@@ -79,13 +81,10 @@ export const GeneratorModal = ({ isOpen, onClose, onApplyLogo, siteDetails, cont
79
81
  !hasNoNextTier &&
80
82
  !hasHistory &&
81
83
  currentLimit - currentUsage < logoCost + promptCreationCost;
82
- // 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.
83
85
  setNeedsFeature(!feature?.hasFeature ?? true);
84
86
  setNeedsMoreRequests(siteNeedsMoreRequests);
85
87
  if (!feature?.hasFeature || siteNeedsMoreRequests) {
86
- const siteUpgradeURL = new URL(`${location.origin}/checkout/${siteDetails?.domain}/${feature?.nextTier?.slug}`);
87
- siteUpgradeURL.searchParams.set('redirect_to', location.href);
88
- setUpgradeURL(siteUpgradeURL.toString());
89
88
  setLoadingState(null);
90
89
  return;
91
90
  }
@@ -142,10 +141,13 @@ export const GeneratorModal = ({ isOpen, onClose, onApplyLogo, siteDetails, cont
142
141
  }
143
142
  // When the site details are set, we need to fetch the feature data.
144
143
  if (!requestedFeatureData.current) {
145
- requestedFeatureData.current = true;
146
- fetchAiAssistantFeature();
144
+ const isRequestingFeature = getIsRequestingAiAssistantFeature();
145
+ if (!isRequestingFeature) {
146
+ requestedFeatureData.current = true;
147
+ fetchAiAssistantFeature();
148
+ }
147
149
  }
148
- }, [siteId, siteDetails, setSiteDetails]);
150
+ }, [siteId, siteDetails, setSiteDetails, getIsRequestingAiAssistantFeature]);
149
151
  // Handles modal opening logic
150
152
  useEffect(() => {
151
153
  // While the modal is not open, the siteId is not set, or the feature data is not available, do nothing.
@@ -163,13 +165,16 @@ export const GeneratorModal = ({ isOpen, onClose, onApplyLogo, siteDetails, cont
163
165
  body = _jsx(FirstLoadScreen, { state: loadingState });
164
166
  }
165
167
  else if (featureFetchError || firstLogoPromptFetchError) {
166
- body = _jsx(FeatureFetchFailureScreen, { onCancel: closeModal, onRetry: initializeModal });
168
+ body = (_jsx(FeatureFetchFailureScreen, { onCancel: closeModal, onRetry: () => {
169
+ closeModal();
170
+ onReload?.();
171
+ } }));
167
172
  }
168
173
  else if (needsFeature || needsMoreRequests) {
169
174
  body = (_jsx(UpgradeScreen, { onCancel: closeModal, upgradeURL: upgradeURL, reason: needsFeature ? 'feature' : 'requests' }));
170
175
  }
171
176
  else {
172
- 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, { onVisitBlankTarget: closeModal }), _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" })] }) })] }))] }));
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" })] }) })] }))] }));
173
178
  }
174
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', {
175
180
  'notice-modal': needsFeature || needsMoreRequests || featureFetchError || firstLogoPromptFetchError,
@@ -53,7 +53,7 @@ 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
58
  const UseOnSiteButton = ({ onApplyLogo, }) => {
59
59
  const { tracks } = useAnalytics();
@@ -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,5 +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
- onVisitBlankTarget: () => void;
8
+ onVisitBlankTarget?: () => void;
9
9
  }>;
@@ -12,5 +12,5 @@ import clsx from 'clsx';
12
12
  import jetpackLogo from '../assets/images/jetpack-logo.svg';
13
13
  import './visit-site-banner.scss';
14
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, children: [__('Learn more about Jetpack AI', 'jetpack-ai-client'), _jsx(Icon, { icon: external, size: 20 })] }) })] })] }));
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
  };
@@ -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
  }
@@ -14,6 +14,7 @@ export interface GeneratorModalProps {
14
14
  isOpen: boolean;
15
15
  onClose: () => void;
16
16
  onApplyLogo: (mediaId: number) => void;
17
+ onReload: () => void;
17
18
  context: string;
18
19
  placement: string;
19
20
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@automattic/jetpack-ai-client",
4
- "version": "0.16.0",
4
+ "version": "0.16.2",
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": {
@@ -23,14 +23,15 @@
23
23
  },
24
24
  "type": "module",
25
25
  "devDependencies": {
26
- "@storybook/addon-actions": "8.1.6",
27
- "@storybook/blocks": "8.1.6",
28
- "@storybook/preview-api": "8.1.6",
29
- "@storybook/react": "8.1.6",
30
- "@types/markdown-it": "14.0.1",
31
- "@types/turndown": "5.0.4",
26
+ "@storybook/addon-actions": "8.2.9",
27
+ "@storybook/blocks": "8.2.9",
28
+ "@storybook/preview-api": "8.2.9",
29
+ "@storybook/react": "8.2.9",
30
+ "@types/markdown-it": "14.1.2",
31
+ "@types/turndown": "5.0.5",
32
32
  "jest": "^29.6.2",
33
33
  "jest-environment-jsdom": "29.7.0",
34
+ "storybook": "8.2.9",
34
35
  "typescript": "5.0.4"
35
36
  },
36
37
  "exports": {
@@ -42,21 +43,21 @@
42
43
  "main": "./build/index.js",
43
44
  "types": "./build/index.d.ts",
44
45
  "dependencies": {
45
- "@automattic/jetpack-base-styles": "^0.6.28",
46
- "@automattic/jetpack-connection": "^0.34.1",
47
- "@automattic/jetpack-shared-extension-utils": "^0.15.0",
46
+ "@automattic/jetpack-base-styles": "^0.6.29",
47
+ "@automattic/jetpack-connection": "^0.35.2",
48
+ "@automattic/jetpack-shared-extension-utils": "^0.15.3",
48
49
  "@microsoft/fetch-event-source": "2.0.1",
49
- "@types/react": "18.3.1",
50
- "@types/wordpress__block-editor": "11.5.14",
51
- "@wordpress/api-fetch": "7.2.0",
52
- "@wordpress/blob": "4.2.0",
53
- "@wordpress/block-editor": "13.2.0",
54
- "@wordpress/components": "28.2.0",
55
- "@wordpress/compose": "7.2.0",
56
- "@wordpress/data": "10.2.0",
57
- "@wordpress/element": "6.2.0",
58
- "@wordpress/i18n": "5.2.0",
59
- "@wordpress/icons": "10.2.0",
50
+ "@types/react": "18.3.3",
51
+ "@types/wordpress__block-editor": "11.5.15",
52
+ "@wordpress/api-fetch": "7.5.0",
53
+ "@wordpress/blob": "4.5.0",
54
+ "@wordpress/block-editor": "14.0.0",
55
+ "@wordpress/components": "28.5.0",
56
+ "@wordpress/compose": "7.5.0",
57
+ "@wordpress/data": "10.5.0",
58
+ "@wordpress/element": "6.5.0",
59
+ "@wordpress/i18n": "5.5.0",
60
+ "@wordpress/icons": "10.5.0",
60
61
  "clsx": "2.1.1",
61
62
  "debug": "4.3.4",
62
63
  "markdown-it": "14.0.0",
@@ -1,4 +1 @@
1
- <svg width="32" height="33" viewBox="0 0 32 33" fill="none" xmlns="http://www.w3.org/2000/svg">
2
- <path d="M16 32.5C24.8366 32.5 32 25.3366 32 16.5C32 7.66344 24.8366 0.5 16 0.5C7.16344 0.5 0 7.66344 0 16.5C0 25.3366 7.16344 32.5 16 32.5Z" fill="#069E08"/>
3
- <path fill-rule="evenodd" clip-rule="evenodd" d="M15.3295 3.67557V19.1565H7.36005L15.3295 3.67557ZM16.9478 29.3244V13.813H24.9478L16.9478 29.3244Z" fill="white"/>
4
- </svg>
1
+ <svg fill="none" height="33" viewBox="0 0 32 33" width="32" xmlns="http://www.w3.org/2000/svg"><path d="m16 32.5c8.8366 0 16-7.1634 16-16 0-8.83656-7.1634-16-16-16-8.83656 0-16 7.16344-16 16 0 8.8366 7.16344 16 16 16z" fill="#069e08"/><path clip-rule="evenodd" d="m15.3295 3.67557v15.48093h-7.96945zm1.6183 25.64883v-15.5114h8z" fill="#fff" fill-rule="evenodd"/></svg>
@@ -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';
@@ -19,6 +19,7 @@ import {
19
19
  EVENT_MODAL_CLOSE,
20
20
  EVENT_GENERATE,
21
21
  } from '../constants.js';
22
+ import { useCheckout } from '../hooks/use-checkout.js';
22
23
  import useLogoGenerator from '../hooks/use-logo-generator.js';
23
24
  import useRequestErrors from '../hooks/use-request-errors.js';
24
25
  import { isLogoHistoryEmpty, clearDeletedMedia } from '../lib/logo-storage.js';
@@ -43,6 +44,7 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
43
44
  isOpen,
44
45
  onClose,
45
46
  onApplyLogo,
47
+ onReload,
46
48
  siteDetails,
47
49
  context,
48
50
  placement,
@@ -50,6 +52,7 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
50
52
  const { tracks } = useAnalytics();
51
53
  const { recordEvent: recordTracksEvent } = tracks;
52
54
  const { setSiteDetails, fetchAiAssistantFeature, loadLogoHistory } = useDispatch( STORE_NAME );
55
+ const { getIsRequestingAiAssistantFeature } = select( STORE_NAME );
53
56
  const [ loadingState, setLoadingState ] = useState<
54
57
  'loadingFeature' | 'analyzing' | 'generating' | null
55
58
  >( null );
@@ -58,12 +61,12 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
58
61
  const requestedFeatureData = useRef< boolean >( false );
59
62
  const [ needsFeature, setNeedsFeature ] = useState( false );
60
63
  const [ needsMoreRequests, setNeedsMoreRequests ] = useState( false );
61
- const [ upgradeURL, setUpgradeURL ] = useState( '' );
62
64
  const { selectedLogo, getAiAssistantFeature, generateFirstPrompt, generateLogo, setContext } =
63
65
  useLogoGenerator();
64
66
  const { featureFetchError, firstLogoPromptFetchError, clearErrors } = useRequestErrors();
65
67
  const siteId = siteDetails?.ID;
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
  }
@@ -182,10 +179,13 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
182
179
 
183
180
  // When the site details are set, we need to fetch the feature data.
184
181
  if ( ! requestedFeatureData.current ) {
185
- requestedFeatureData.current = true;
186
- fetchAiAssistantFeature();
182
+ const isRequestingFeature = getIsRequestingAiAssistantFeature();
183
+ if ( ! isRequestingFeature ) {
184
+ requestedFeatureData.current = true;
185
+ fetchAiAssistantFeature();
186
+ }
187
187
  }
188
- }, [ siteId, siteDetails, setSiteDetails ] );
188
+ }, [ siteId, siteDetails, setSiteDetails, getIsRequestingAiAssistantFeature ] );
189
189
 
190
190
  // Handles modal opening logic
191
191
  useEffect( () => {
@@ -206,7 +206,15 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
206
206
  if ( loadingState ) {
207
207
  body = <FirstLoadScreen state={ loadingState } />;
208
208
  } else if ( featureFetchError || firstLogoPromptFetchError ) {
209
- body = <FeatureFetchFailureScreen onCancel={ closeModal } onRetry={ initializeModal } />;
209
+ body = (
210
+ <FeatureFetchFailureScreen
211
+ onCancel={ closeModal }
212
+ onRetry={ () => {
213
+ closeModal();
214
+ onReload?.();
215
+ } }
216
+ />
217
+ );
210
218
  } else if ( needsFeature || needsMoreRequests ) {
211
219
  body = (
212
220
  <UpgradeScreen
@@ -227,7 +235,7 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
227
235
  />
228
236
  { logoAccepted ? (
229
237
  <div className="jetpack-ai-logo-generator__accept">
230
- <VisitSiteBanner onVisitBlankTarget={ closeModal } />
238
+ <VisitSiteBanner />
231
239
  <div className="jetpack-ai-logo-generator__accept-actions">
232
240
  <Button variant="primary" onClick={ closeModal }>
233
241
  { __( 'Close', 'jetpack-ai-client' ) }
@@ -79,10 +79,10 @@ 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
 
@@ -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,7 +17,7 @@ import type React from 'react';
17
17
 
18
18
  export const VisitSiteBanner: React.FC< {
19
19
  className?: string;
20
- onVisitBlankTarget: () => void;
20
+ onVisitBlankTarget?: () => void;
21
21
  } > = ( { className = null, onVisitBlankTarget } ) => {
22
22
  return (
23
23
  <div className={ clsx( 'jetpack-ai-logo-generator-modal-visit-site-banner', className ) }>
@@ -42,7 +42,7 @@ export const VisitSiteBanner: React.FC< {
42
42
  variant="link"
43
43
  href="https://jetpack.com/redirect/?source=logo_generator_learn_more_about_jetpack_ai"
44
44
  target="_blank"
45
- onClick={ onVisitBlankTarget }
45
+ onClick={ onVisitBlankTarget ? onVisitBlankTarget : null }
46
46
  >
47
47
  { __( 'Learn more about Jetpack AI', 'jetpack-ai-client' ) }
48
48
  <Icon icon={ external } size={ 20 } />
@@ -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
  };
@@ -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
  }
@@ -16,6 +16,7 @@ export interface GeneratorModalProps {
16
16
  isOpen: boolean;
17
17
  onClose: () => void;
18
18
  onApplyLogo: ( mediaId: number ) => void;
19
+ onReload: () => void;
19
20
  context: string;
20
21
  placement: string;
21
22
  }