@automattic/jetpack-ai-client 0.14.6 → 0.15.0

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.
Files changed (128) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/build/ask-question/sync.d.ts +2 -8
  3. package/build/ask-question/sync.js +20 -19
  4. package/build/hooks/use-image-generator/index.js +1 -1
  5. package/build/hooks/use-save-to-media-library/index.d.ts +12 -0
  6. package/build/hooks/use-save-to-media-library/index.js +74 -0
  7. package/build/index.d.ts +2 -0
  8. package/build/index.js +5 -0
  9. package/build/libs/index.d.ts +1 -1
  10. package/build/libs/index.js +1 -1
  11. package/build/libs/markdown/index.d.ts +2 -2
  12. package/build/libs/markdown/index.js +2 -2
  13. package/build/libs/markdown/markdown-to-html.d.ts +8 -1
  14. package/build/libs/markdown/markdown-to-html.js +10 -1
  15. package/build/logo-generator/assets/icons/ai.d.ts +6 -0
  16. package/build/logo-generator/assets/icons/ai.js +8 -0
  17. package/build/logo-generator/assets/icons/check.d.ts +6 -0
  18. package/build/logo-generator/assets/icons/check.js +8 -0
  19. package/build/logo-generator/assets/icons/logo.d.ts +6 -0
  20. package/build/logo-generator/assets/icons/logo.js +8 -0
  21. package/build/logo-generator/assets/icons/media.d.ts +6 -0
  22. package/build/logo-generator/assets/icons/media.js +8 -0
  23. package/build/logo-generator/components/feature-fetch-failure-screen.d.ts +8 -0
  24. package/build/logo-generator/components/feature-fetch-failure-screen.js +10 -0
  25. package/build/logo-generator/components/first-load-screen.d.ts +5 -0
  26. package/build/logo-generator/components/first-load-screen.js +16 -0
  27. package/build/logo-generator/components/generator-modal.d.ts +7 -0
  28. package/build/logo-generator/components/generator-modal.js +184 -0
  29. package/build/logo-generator/components/history-carousel.d.ts +6 -0
  30. package/build/logo-generator/components/history-carousel.js +36 -0
  31. package/build/logo-generator/components/image-loader.d.ts +7 -0
  32. package/build/logo-generator/components/image-loader.js +12 -0
  33. package/build/logo-generator/components/logo-presenter.d.ts +4 -0
  34. package/build/logo-generator/components/logo-presenter.js +106 -0
  35. package/build/logo-generator/components/prompt.d.ts +5 -0
  36. package/build/logo-generator/components/prompt.js +96 -0
  37. package/build/logo-generator/components/upgrade-nudge.d.ts +2 -0
  38. package/build/logo-generator/components/upgrade-nudge.js +30 -0
  39. package/build/logo-generator/components/upgrade-screen.d.ts +9 -0
  40. package/build/logo-generator/components/upgrade-screen.js +24 -0
  41. package/build/logo-generator/components/visit-site-banner.d.ts +10 -0
  42. package/build/logo-generator/components/visit-site-banner.js +16 -0
  43. package/build/logo-generator/constants.d.ts +16 -0
  44. package/build/logo-generator/constants.js +19 -0
  45. package/build/logo-generator/hooks/use-checkout.d.ts +4 -0
  46. package/build/logo-generator/hooks/use-checkout.js +26 -0
  47. package/build/logo-generator/hooks/use-logo-generator.d.ts +46 -0
  48. package/build/logo-generator/hooks/use-logo-generator.js +286 -0
  49. package/build/logo-generator/hooks/use-request-errors.d.ts +16 -0
  50. package/build/logo-generator/hooks/use-request-errors.js +46 -0
  51. package/build/logo-generator/index.d.ts +1 -0
  52. package/build/logo-generator/index.js +1 -0
  53. package/build/logo-generator/lib/logo-storage.d.ts +58 -0
  54. package/build/logo-generator/lib/logo-storage.js +123 -0
  55. package/build/logo-generator/lib/media-exists.d.ts +12 -0
  56. package/build/logo-generator/lib/media-exists.js +33 -0
  57. package/build/logo-generator/lib/set-site-logo.d.ts +13 -0
  58. package/build/logo-generator/lib/set-site-logo.js +26 -0
  59. package/build/logo-generator/lib/wpcom-limited-request.d.ts +7 -0
  60. package/build/logo-generator/lib/wpcom-limited-request.js +33 -0
  61. package/build/logo-generator/store/actions.d.ts +105 -0
  62. package/build/logo-generator/store/actions.js +193 -0
  63. package/build/logo-generator/store/constants.d.ts +44 -0
  64. package/build/logo-generator/store/constants.js +44 -0
  65. package/build/logo-generator/store/index.d.ts +1 -0
  66. package/build/logo-generator/store/index.js +19 -0
  67. package/build/logo-generator/store/initial-state.d.ts +3 -0
  68. package/build/logo-generator/store/initial-state.js +40 -0
  69. package/build/logo-generator/store/reducer.d.ts +347 -0
  70. package/build/logo-generator/store/reducer.js +293 -0
  71. package/build/logo-generator/store/selectors.d.ts +119 -0
  72. package/build/logo-generator/store/selectors.js +173 -0
  73. package/build/logo-generator/store/types.d.ts +164 -0
  74. package/build/logo-generator/store/types.js +1 -0
  75. package/build/logo-generator/types.d.ts +82 -0
  76. package/build/logo-generator/types.js +1 -0
  77. package/build/types.d.ts +6 -0
  78. package/package.json +5 -3
  79. package/src/ask-question/sync.ts +22 -27
  80. package/src/hooks/use-image-generator/index.ts +1 -1
  81. package/src/hooks/use-save-to-media-library/index.ts +95 -0
  82. package/src/index.ts +6 -0
  83. package/src/libs/index.ts +1 -0
  84. package/src/libs/markdown/index.ts +2 -2
  85. package/src/libs/markdown/markdown-to-html.ts +20 -3
  86. package/src/logo-generator/assets/icons/ai.tsx +21 -0
  87. package/src/logo-generator/assets/icons/check.tsx +23 -0
  88. package/src/logo-generator/assets/icons/icons.scss +5 -0
  89. package/src/logo-generator/assets/icons/logo.tsx +23 -0
  90. package/src/logo-generator/assets/icons/media.tsx +24 -0
  91. package/src/logo-generator/assets/images/jetpack-logo.svg +4 -0
  92. package/src/logo-generator/assets/images/loader.gif +0 -0
  93. package/src/logo-generator/assets/index.d.ts +3 -0
  94. package/src/logo-generator/components/feature-fetch-failure-screen.tsx +35 -0
  95. package/src/logo-generator/components/first-load-screen.scss +12 -0
  96. package/src/logo-generator/components/first-load-screen.tsx +32 -0
  97. package/src/logo-generator/components/generator-modal.scss +92 -0
  98. package/src/logo-generator/components/generator-modal.tsx +291 -0
  99. package/src/logo-generator/components/history-carousel.scss +36 -0
  100. package/src/logo-generator/components/history-carousel.tsx +57 -0
  101. package/src/logo-generator/components/image-loader.tsx +22 -0
  102. package/src/logo-generator/components/logo-presenter.scss +116 -0
  103. package/src/logo-generator/components/logo-presenter.tsx +234 -0
  104. package/src/logo-generator/components/prompt.scss +102 -0
  105. package/src/logo-generator/components/prompt.tsx +211 -0
  106. package/src/logo-generator/components/upgrade-nudge.scss +43 -0
  107. package/src/logo-generator/components/upgrade-nudge.tsx +58 -0
  108. package/src/logo-generator/components/upgrade-screen.tsx +67 -0
  109. package/src/logo-generator/components/visit-site-banner.scss +29 -0
  110. package/src/logo-generator/components/visit-site-banner.tsx +50 -0
  111. package/src/logo-generator/constants.ts +22 -0
  112. package/src/logo-generator/hooks/use-checkout.ts +37 -0
  113. package/src/logo-generator/hooks/use-logo-generator.ts +389 -0
  114. package/src/logo-generator/hooks/use-request-errors.ts +70 -0
  115. package/src/logo-generator/index.ts +1 -0
  116. package/src/logo-generator/lib/logo-storage.ts +166 -0
  117. package/src/logo-generator/lib/media-exists.ts +42 -0
  118. package/src/logo-generator/lib/set-site-logo.ts +32 -0
  119. package/src/logo-generator/lib/wpcom-limited-request.ts +41 -0
  120. package/src/logo-generator/store/actions.ts +251 -0
  121. package/src/logo-generator/store/constants.ts +49 -0
  122. package/src/logo-generator/store/index.ts +25 -0
  123. package/src/logo-generator/store/initial-state.ts +43 -0
  124. package/src/logo-generator/store/reducer.ts +387 -0
  125. package/src/logo-generator/store/selectors.ts +201 -0
  126. package/src/logo-generator/store/types.ts +207 -0
  127. package/src/logo-generator/types.ts +97 -0
  128. package/src/types.ts +8 -0
@@ -0,0 +1,291 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { useAnalytics } from '@automattic/jetpack-shared-extension-utils';
5
+ import { Modal, Button } from '@wordpress/components';
6
+ import { useDispatch } from '@wordpress/data';
7
+ import { __ } from '@wordpress/i18n';
8
+ import { external, Icon } from '@wordpress/icons';
9
+ import clsx from 'clsx';
10
+ import debugFactory from 'debug';
11
+ import { useState, useEffect, useCallback, useRef } from 'react';
12
+ /**
13
+ * Internal dependencies
14
+ */
15
+ import {
16
+ DEFAULT_LOGO_COST,
17
+ EVENT_MODAL_OPEN,
18
+ EVENT_FEEDBACK,
19
+ EVENT_MODAL_CLOSE,
20
+ EVENT_PLACEMENT_QUICK_LINKS,
21
+ EVENT_GENERATE,
22
+ } from '../constants.js';
23
+ import useLogoGenerator from '../hooks/use-logo-generator.js';
24
+ import useRequestErrors from '../hooks/use-request-errors.js';
25
+ import { isLogoHistoryEmpty, clearDeletedMedia } from '../lib/logo-storage.js';
26
+ import { STORE_NAME } from '../store/index.js';
27
+ import { FeatureFetchFailureScreen } from './feature-fetch-failure-screen.js';
28
+ import { FirstLoadScreen } from './first-load-screen.js';
29
+ import { HistoryCarousel } from './history-carousel.js';
30
+ import { LogoPresenter } from './logo-presenter.js';
31
+ import { Prompt } from './prompt.js';
32
+ import { UpgradeScreen } from './upgrade-screen.js';
33
+ import { VisitSiteBanner } from './visit-site-banner.js';
34
+ import './generator-modal.scss';
35
+ /**
36
+ * Types
37
+ */
38
+ import type { GeneratorModalProps } from '../types.js';
39
+ import type React from 'react';
40
+
41
+ const debug = debugFactory( 'jetpack-ai-calypso:generator-modal' );
42
+
43
+ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
44
+ isOpen,
45
+ onClose,
46
+ siteDetails,
47
+ context,
48
+ } ) => {
49
+ const { tracks } = useAnalytics();
50
+ const { recordEvent: recordTracksEvent } = tracks;
51
+ const { setSiteDetails, fetchAiAssistantFeature, loadLogoHistory } = useDispatch( STORE_NAME );
52
+ const [ loadingState, setLoadingState ] = useState<
53
+ 'loadingFeature' | 'analyzing' | 'generating' | null
54
+ >( null );
55
+ const [ initialPrompt, setInitialPrompt ] = useState< string | undefined >();
56
+ const needsToHandleModalOpen = useRef< boolean >( true );
57
+ const requestedFeatureData = useRef< boolean >( false );
58
+ const [ needsFeature, setNeedsFeature ] = useState( false );
59
+ const [ needsMoreRequests, setNeedsMoreRequests ] = useState( false );
60
+ const [ upgradeURL, setUpgradeURL ] = useState( '' );
61
+ const { selectedLogo, getAiAssistantFeature, generateFirstPrompt, generateLogo, setContext } =
62
+ useLogoGenerator();
63
+ const { featureFetchError, firstLogoPromptFetchError, clearErrors } = useRequestErrors();
64
+ const siteId = siteDetails?.ID;
65
+ const siteURL = siteDetails?.URL;
66
+ const [ logoAccepted, setLogoAccepted ] = useState( false );
67
+
68
+ // First fetch the feature data so we have the most up-to-date info from the backend.
69
+ const feature = getAiAssistantFeature();
70
+
71
+ const generateFirstLogo = useCallback( async () => {
72
+ try {
73
+ // First generate the prompt based on the site's data.
74
+ setLoadingState( 'analyzing' );
75
+ recordTracksEvent( EVENT_GENERATE, { context, tool: 'first-prompt' } );
76
+ const prompt = await generateFirstPrompt();
77
+ setInitialPrompt( prompt );
78
+
79
+ // Then generate the logo based on the prompt.
80
+ setLoadingState( 'generating' );
81
+ await generateLogo( { prompt } );
82
+ setLoadingState( null );
83
+ } catch ( error ) {
84
+ debug( 'Error generating first logo', error );
85
+ setLoadingState( null );
86
+ }
87
+ }, [ context, generateFirstPrompt, generateLogo ] );
88
+
89
+ /*
90
+ * Called ONCE to check the feature data to make sure the site is allowed to do the generation.
91
+ * Also, checks site history and trigger a new generation in case there are no logos to present.
92
+ */
93
+ const initializeModal = useCallback( async () => {
94
+ try {
95
+ const hasHistory = ! isLogoHistoryEmpty( String( siteId ) );
96
+ const logoCost = feature?.costs?.[ 'jetpack-ai-logo-generator' ]?.logo ?? DEFAULT_LOGO_COST;
97
+ const promptCreationCost = 1;
98
+ const currentLimit = feature?.currentTier?.value || 0;
99
+ const currentUsage = feature?.usagePeriod?.requestsCount || 0;
100
+ const isUnlimited = currentLimit === 1;
101
+ const hasNoNextTier = ! feature?.nextTier; // If there is no next tier, the user cannot upgrade.
102
+
103
+ // The user needs an upgrade immediately if they have no logos and not enough requests remaining for one prompt and one logo generation.
104
+ const siteNeedsMoreRequests =
105
+ ! isUnlimited &&
106
+ ! hasNoNextTier &&
107
+ ! hasHistory &&
108
+ currentLimit - currentUsage < logoCost + promptCreationCost;
109
+
110
+ // If the site requires an upgrade, set the upgrade URL and show the upgrade screen immediately.
111
+ setNeedsFeature( ! feature?.hasFeature ?? true );
112
+ setNeedsMoreRequests( siteNeedsMoreRequests );
113
+
114
+ 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
+ setLoadingState( null );
121
+ return;
122
+ }
123
+
124
+ // Load the logo history and clear any deleted media.
125
+ await clearDeletedMedia( String( siteId ) );
126
+ loadLogoHistory( siteId );
127
+
128
+ // If there is any logo, we do not need to generate a first logo again.
129
+ if ( ! isLogoHistoryEmpty( String( siteId ) ) ) {
130
+ setLoadingState( null );
131
+ return;
132
+ }
133
+
134
+ // If the site does not require an upgrade and has no logos stored, generate the first prompt based on the site's data.
135
+ generateFirstLogo();
136
+ } catch ( error ) {
137
+ debug( 'Error fetching feature', error );
138
+ setLoadingState( null );
139
+ }
140
+ }, [
141
+ feature,
142
+ generateFirstLogo,
143
+ loadLogoHistory,
144
+ clearDeletedMedia,
145
+ isLogoHistoryEmpty,
146
+ siteId,
147
+ ] );
148
+
149
+ const handleModalOpen = useCallback( async () => {
150
+ setContext( context );
151
+ recordTracksEvent( EVENT_MODAL_OPEN, { context, placement: EVENT_PLACEMENT_QUICK_LINKS } );
152
+
153
+ initializeModal();
154
+ }, [ setContext, context, initializeModal ] );
155
+
156
+ const closeModal = () => {
157
+ // Reset the state when the modal is closed, so we trigger the modal initialization again when it's opened.
158
+ needsToHandleModalOpen.current = true;
159
+ onClose();
160
+ setLoadingState( null );
161
+ setNeedsFeature( false );
162
+ setNeedsMoreRequests( false );
163
+ clearErrors();
164
+ setLogoAccepted( false );
165
+ recordTracksEvent( EVENT_MODAL_CLOSE, { context, placement: EVENT_PLACEMENT_QUICK_LINKS } );
166
+ };
167
+
168
+ const handleApplyLogo = () => {
169
+ 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 );
179
+ };
180
+
181
+ const handleFeedbackClick = () => {
182
+ recordTracksEvent( EVENT_FEEDBACK, { context } );
183
+ };
184
+
185
+ // Set site details when siteId changes
186
+ useEffect( () => {
187
+ if ( siteId ) {
188
+ setSiteDetails( siteDetails );
189
+ }
190
+
191
+ // When the site details are set, we need to fetch the feature data.
192
+ if ( ! requestedFeatureData.current ) {
193
+ requestedFeatureData.current = true;
194
+ fetchAiAssistantFeature();
195
+ }
196
+ }, [ siteId, siteDetails, setSiteDetails ] );
197
+
198
+ // Handles modal opening logic
199
+ useEffect( () => {
200
+ // While the modal is not open, the siteId is not set, or the feature data is not available, do nothing.
201
+ if ( ! isOpen || ! siteId || ! feature?.costs ) {
202
+ return;
203
+ }
204
+
205
+ // Prevent multiple calls of the handleModalOpen function
206
+ if ( needsToHandleModalOpen.current ) {
207
+ needsToHandleModalOpen.current = false;
208
+ handleModalOpen();
209
+ }
210
+ }, [ isOpen, siteId, handleModalOpen, feature ] );
211
+
212
+ let body: React.ReactNode;
213
+
214
+ if ( loadingState ) {
215
+ body = <FirstLoadScreen state={ loadingState } />;
216
+ } else if ( featureFetchError || firstLogoPromptFetchError ) {
217
+ body = <FeatureFetchFailureScreen onCancel={ closeModal } onRetry={ initializeModal } />;
218
+ } else if ( needsFeature || needsMoreRequests ) {
219
+ body = (
220
+ <UpgradeScreen
221
+ onCancel={ closeModal }
222
+ upgradeURL={ upgradeURL }
223
+ reason={ needsFeature ? 'feature' : 'requests' }
224
+ />
225
+ );
226
+ } else {
227
+ body = (
228
+ <>
229
+ { ! logoAccepted && <Prompt initialPrompt={ initialPrompt } /> }
230
+ <LogoPresenter
231
+ logo={ selectedLogo }
232
+ onApplyLogo={ handleApplyLogo }
233
+ logoAccepted={ logoAccepted }
234
+ siteId={ String( siteId ) }
235
+ />
236
+ { logoAccepted ? (
237
+ <div className="jetpack-ai-logo-generator__accept">
238
+ <VisitSiteBanner siteURL={ siteURL } onVisitBlankTarget={ handleCloseAndReload } />
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' ) }
245
+ </Button>
246
+ </div>
247
+ </div>
248
+ ) : (
249
+ <>
250
+ <HistoryCarousel />
251
+ <div className="jetpack-ai-logo-generator__footer">
252
+ <Button
253
+ variant="link"
254
+ className="jetpack-ai-logo-generator__feedback-button"
255
+ href="https://jetpack.com/redirect/?source=jetpack-ai-feedback"
256
+ target="_blank"
257
+ onClick={ handleFeedbackClick }
258
+ >
259
+ <span>{ __( 'Provide feedback', 'jetpack-ai-client' ) }</span>
260
+ <Icon icon={ external } className="icon" />
261
+ </Button>
262
+ </div>
263
+ </>
264
+ ) }
265
+ </>
266
+ );
267
+ }
268
+
269
+ return (
270
+ <>
271
+ { isOpen && (
272
+ <Modal
273
+ className="jetpack-ai-logo-generator-modal"
274
+ onRequestClose={ logoAccepted ? handleCloseAndReload : closeModal }
275
+ shouldCloseOnClickOutside={ false }
276
+ shouldCloseOnEsc={ false }
277
+ title={ __( 'Jetpack AI Logo Generator', 'jetpack-ai-client' ) }
278
+ >
279
+ <div
280
+ className={ clsx( 'jetpack-ai-logo-generator-modal__body', {
281
+ 'notice-modal':
282
+ needsFeature || needsMoreRequests || featureFetchError || firstLogoPromptFetchError,
283
+ } ) }
284
+ >
285
+ { body }
286
+ </div>
287
+ </Modal>
288
+ ) }
289
+ </>
290
+ );
291
+ };
@@ -0,0 +1,36 @@
1
+ @import '@automattic/jetpack-base-styles/root-variables';
2
+
3
+ .jetpack-ai-logo-generator__carousel {
4
+ display: flex;
5
+ gap: 8px;
6
+ overflow-x: auto;
7
+ flex-shrink: 0;
8
+
9
+ .components-button {
10
+ height: unset;
11
+ padding: unset;
12
+ }
13
+
14
+ @media (min-width: 700px) {
15
+ padding: 2px;
16
+ }
17
+ }
18
+
19
+ .jetpack-ai-logo-generator__carousel-logo {
20
+ display: flex;
21
+ justify-content: center;
22
+ align-items: center;
23
+ border: 1px solid var(--studio-gray-5, #dcdcde);
24
+ border-radius: 2px;
25
+ flex-shrink: 0;
26
+
27
+ &.is-selected {
28
+ border-color: var(--color-link, #3858e9);
29
+ border-width: 1.5px;
30
+ }
31
+
32
+ img {
33
+ width: 48px;
34
+ height: 48px;
35
+ }
36
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { useAnalytics } from '@automattic/jetpack-shared-extension-utils';
5
+ import { Button } from '@wordpress/components';
6
+ import clsx from 'clsx';
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import { EVENT_NAVIGATE } from '../constants.js';
11
+ import useLogoGenerator from '../hooks/use-logo-generator.js';
12
+ import './history-carousel.scss';
13
+ /**
14
+ * Types
15
+ */
16
+ import type React from 'react';
17
+
18
+ export const HistoryCarousel: React.FC = () => {
19
+ const { tracks } = useAnalytics();
20
+ const { recordEvent: recordTracksEvent } = tracks;
21
+ const { logos, selectedLogo, setSelectedLogoIndex, context } = useLogoGenerator();
22
+
23
+ const handleClick = ( index: number ) => {
24
+ recordTracksEvent( EVENT_NAVIGATE, {
25
+ context,
26
+ logos_count: logos.length,
27
+ selected_logo: index + 1,
28
+ } );
29
+ setSelectedLogoIndex( index );
30
+ };
31
+
32
+ const thumbnailFrom = ( url: string ): string => {
33
+ const thumbnailURL = new URL( url );
34
+
35
+ if ( ! thumbnailURL.searchParams.has( 'resize' ) ) {
36
+ thumbnailURL.searchParams.append( 'resize', '48,48' );
37
+ }
38
+
39
+ return thumbnailURL.toString();
40
+ };
41
+
42
+ return (
43
+ <div className="jetpack-ai-logo-generator__carousel">
44
+ { logos.map( ( logo, index ) => (
45
+ <Button
46
+ key={ logo.url }
47
+ className={ clsx( 'jetpack-ai-logo-generator__carousel-logo', {
48
+ 'is-selected': logo.url === selectedLogo.url,
49
+ } ) }
50
+ onClick={ () => handleClick( index ) }
51
+ >
52
+ <img src={ thumbnailFrom( logo.url ) } alt={ logo.description } />
53
+ </Button>
54
+ ) ) }
55
+ </div>
56
+ );
57
+ };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import clsx from 'clsx';
5
+ /**
6
+ * Internal dependencies
7
+ */
8
+ import loader from '../assets/images/loader.gif';
9
+ /**
10
+ * Types
11
+ */
12
+ import type React from 'react';
13
+
14
+ export const ImageLoader: React.FC< { className?: string } > = ( { className = null } ) => {
15
+ return (
16
+ <img
17
+ src={ loader }
18
+ alt="Loading"
19
+ className={ clsx( 'jetpack-ai-logo-generator-modal__loader', className ) }
20
+ />
21
+ );
22
+ };
@@ -0,0 +1,116 @@
1
+ @import '@automattic/jetpack-base-styles/root-variables';
2
+
3
+ .jetpack-ai-logo-generator-modal-presenter__wrapper {
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: 8px;
7
+ }
8
+
9
+ .jetpack-ai-logo-generator-modal-presenter {
10
+ display: flex;
11
+ }
12
+
13
+ .jetpack-ai-logo-generator-modal-presenter__content {
14
+ border-radius: 4px;
15
+ background: var(--studio-gray-0, #f6f7f7);
16
+ display: flex;
17
+ align-items: center;
18
+ flex-grow: 1;
19
+ max-height: 229px;
20
+
21
+ @media (max-width: 700px) {
22
+ flex-direction: column;
23
+ max-height: unset;
24
+ }
25
+ }
26
+
27
+ .jetpack-ai-logo-generator-modal-presenter__rectangle {
28
+ position: relative;
29
+ width: 0;
30
+
31
+ &::after {
32
+ width: 15px;
33
+ height: 15px;
34
+ transform: rotate(-45deg);
35
+ background-color: var(--studio-gray-0, #f6f7f7);
36
+ content: "";
37
+ position: absolute;
38
+ top: -7.5px;
39
+ left: -66px;
40
+ }
41
+ }
42
+
43
+ .jetpack-ai-logo-generator-modal-presenter__loading-text {
44
+ flex-grow: 1;
45
+ text-align: center;
46
+ }
47
+
48
+ .jetpack-ai-logo-generator-modal-presenter__logo {
49
+ width: 198px;
50
+ height: 198px;
51
+ margin: 16px 30px 16px 16px;
52
+
53
+ @media (max-width: 700px) {
54
+ margin: 16px;
55
+ }
56
+ }
57
+
58
+ .jetpack-ai-logo-generator-modal-presenter__action-wrapper {
59
+ height: 100%;
60
+ flex-grow: 1;
61
+ margin-right: 32px;
62
+ display: flex;
63
+ flex-direction: column;
64
+ padding: 16px 0;
65
+ box-sizing: border-box;
66
+
67
+ @media (max-width: 700px) {
68
+ margin-left: 32px;
69
+ }
70
+ }
71
+
72
+ .jetpack-ai-logo-generator-modal-presenter__description {
73
+ flex-grow: 1;
74
+ padding-top: 16px;
75
+ color: var(--studio-gray-50, #646970);
76
+ overflow-y: auto;
77
+ }
78
+
79
+ .jetpack-ai-logo-generator-modal-presenter__actions {
80
+ padding-top: 16px;
81
+ margin-top: 16px;
82
+ border-top: 1px solid var(--studio-gray-5, #dcdcde);
83
+ display: flex;
84
+ gap: 24px;
85
+ }
86
+
87
+ .jetpack-ai-logo-generator-modal-presenter__action {
88
+ display: flex;
89
+ align-items: center;
90
+
91
+ &.components-button,
92
+ &.components-button:hover,
93
+ &.components-button:active {
94
+ color: var(--color-link, #3858e9);
95
+ }
96
+
97
+ .action-text {
98
+ font-size: var(--font-body-extra-small);
99
+ }
100
+
101
+ .jetpack-ai-logo-generator-icon {
102
+ margin-right: 8px;
103
+ }
104
+ }
105
+
106
+ .jetpack-ai-logo-generator-modal-presenter__success-wrapper {
107
+ display: flex;
108
+ flex-direction: column;
109
+ align-items: center;
110
+ gap: 16px;
111
+ width: 100%;
112
+
113
+ @media (max-width: 700px) {
114
+ padding-bottom: 16px;
115
+ }
116
+ }