PluginProbe
Extendify / 3.2.2
Extendify v3.2.2
3.2.2 3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 All 128 releases
← All changes | src/AutoLaunch/components/DescriptionGathering.jsx +5 -323 3.1.4 → 3.2.2 View file →
@@ -1,324 +1,6 @@
1 -import { getExtendifyCodeRecommendation } from '@auto-launch/functions/extendify-code';
2 -import { getAbTest } from '@auto-launch/functions/getAbTest';
3 -import { fetchWithTimeout } from '@auto-launch/functions/helpers';
4 -import { useInstallRequiredPlugins } from '@auto-launch/hooks/useInstallRequiredPlugins';
5 -import { loaderThreeDots } from '@auto-launch/icons';
6 -import { useLaunchDataStore } from '@auto-launch/state/launch-data';
7 -import { AI_HOST } from '@constants';
8 -import { reqDataBasics } from '@shared/lib/data';
9 -import { useAIConsentStore } from '@shared/state/ai-consent';
10 -import {
11 - forwardRef,
12 - useCallback,
13 - useEffect,
14 - useRef,
15 - useState,
16 -} from '@wordpress/element';
17 -import { decodeEntities } from '@wordpress/html-entities';
18 -import { __ } from '@wordpress/i18n';
19 -import { chevronRight, Icon, pencil } from '@wordpress/icons';
20 -import { isURL } from '@wordpress/url';
1 +import { DescriptionForm } from '@auto-launch/components/DescriptionForm';
2 +import { useDescriptionForm } from '@auto-launch/hooks/useDescriptionForm';
21 3
22 -const getShowTitle = () => Boolean(window.extLaunchData?.showLaunchTitle);
23 -
24 -const getSubmitOutside = () =>
25 - getAbTest('AutoLaunch.SubmitOutside').variant === 'B' || getShowTitle();
26 -
27 -export const DescriptionGathering = () => {
28 - const { setData, descriptionBackup, urlParams } = useLaunchDataStore();
29 - useInstallRequiredPlugins();
30 - const [input, setInput] = useState(
31 - urlParams.description ||
32 - (!getShowTitle() && urlParams.title) ||
33 - descriptionBackup ||
34 - '',
35 - );
36 - const blogname = window.extSharedData?.siteTitle || '';
37 - const titlePrefill =
38 - !blogname || isURL(blogname) ? '' : decodeEntities(blogname);
39 - const [title, setTitle] = useState(urlParams.title || titlePrefill);
40 - const [improving, setImproving] = useState(false);
41 - const [checking, setChecking] = useState(false);
42 - const [lastImproved, setLastImproved] = useState(null);
43 - const textareaRef = useRef(null);
44 - const { consentTerms } = useAIConsentStore();
45 - // Showing the title field makes the description optional, so the submit
46 - // gate and the textarea autofocus both follow it.
47 - const showTitle = getShowTitle();
48 - const submitDisabled =
49 - checking ||
50 - (showTitle ? title.trim().length === 0 : input.trim().length === 0);
51 - const placeholder = useDescriptionPlaceholder();
52 -
53 - // resize the height of the textarea based on the content
54 - const adjustHeight = useCallback(() => {
55 - const el = textareaRef.current;
56 - if (!el) return;
57 - const bottomPadding = 120; // tweak as needed
58 - // Reset to measure natural height
59 - el.style.height = 'auto';
60 -
61 - const rect = el.getBoundingClientRect();
62 - const viewportHeight = window.innerHeight;
63 -
64 - const maxAvailable = Math.max(0, viewportHeight - rect.top - bottomPadding);
65 - const desired = el.scrollHeight;
66 - const nextHeight = Math.min(desired, maxAvailable);
67 -
68 - el.style.height = `${nextHeight}px`;
69 - el.style.overflowY = desired > maxAvailable ? 'auto' : 'hidden';
70 -
71 - // Notify others
72 - window.dispatchEvent(new Event('launch-textarea-resize'));
73 - }, []);
74 -
75 - const submitForm = async (e) => {
76 - e.preventDefault();
77 - const trimmedTitle = title.trim();
78 - const trimmedInput = input.trim();
79 - if (showTitle) setData('title', trimmedTitle);
80 - setData('descriptionRaw', trimmedInput);
81 -
82 - // Only `showExtendifyCode` partners pay the classification latency; on a
83 - // `1` we divert to the connector screen instead of starting site creation.
84 - if (window.extSharedData?.showExtendifyCode) {
85 - setChecking(true);
86 - const recommend = await getExtendifyCodeRecommendation(
87 - trimmedInput || trimmedTitle,
88 - );
89 - if (recommend === 1) {
90 - // Leave `checking` on so the loading state holds through the exit
91 - // transition instead of flashing the textarea before the connector.
92 - setData('showExtendifyCodeScreen', true);
93 - return;
94 - }
95 - setChecking(false);
96 - }
97 - setData('go', true);
98 - };
99 -
100 - const handleImprove = async () => {
101 - setImproving(true);
102 - const url = `${AI_HOST}/api/prompt/improve`;
103 - const method = 'POST';
104 - const headers = { 'Content-Type': 'application/json' };
105 - const response = await fetchWithTimeout(url, {
106 - method,
107 - headers,
108 - body: JSON.stringify({
109 - ...reqDataBasics,
110 - description: input.trim(),
111 - title: window.extSharedData.siteTitle,
112 - }),
113 - })
114 - .then((res) => res.ok && res.json())
115 - .catch(() => null);
116 - const nextValue = response?.improvedPrompt;
117 - setImproving(false);
118 - if (nextValue) {
119 - setLastImproved(nextValue);
120 - const el = textareaRef.current;
121 - if (!el) return setInput(nextValue);
122 - requestAnimationFrame(() => {
123 - // Preserve undo ability by using native events instead of React state
124 - el.focus();
125 - el.select();
126 - const ok = document.execCommand('insertText', false, nextValue);
127 - if (!ok) setInput(nextValue);
128 - });
129 - }
130 - };
131 -
132 - useEffect(() => {
133 - setData('descriptionBackup', input.trim());
134 - const raf = requestAnimationFrame(() => {
135 - adjustHeight();
136 - });
137 - return () => cancelAnimationFrame(raf);
138 - }, [input, setData]);
139 -
140 - useEffect(() => {
141 - const controller = new AbortController();
142 - const { signal } = controller;
143 - const handleResize = () => {
144 - adjustHeight();
145 - const c = textareaRef.current;
146 - c?.scrollTo(0, c.scrollHeight);
147 - };
148 - window.addEventListener('resize', handleResize, { signal });
149 - window.addEventListener('orientationchange', handleResize, { signal });
150 - adjustHeight();
151 - return () => controller.abort();
152 - }, [adjustHeight]);
153 -
154 - return (
155 - <>
156 - {/* biome-ignore lint: allow onClick without keyboard */}
157 - <form
158 - onSubmit={submitForm}
159 - onClick={() => textareaRef.current?.focus()}
160 - className="relative flex w-full flex-col"
161 - >
162 - <TitleField
163 - title={title}
164 - setTitle={setTitle}
165 - setData={setData}
166 - improving={improving}
167 - />
168 - <div className="w-full rounded-3xl border border-gray-300 bg-gray-100/80 text-gray-900 backdrop-blur-2xl focus-within:border-gray-500 focus-within:ring-gray-500 shadow-md overflow-hidden">
169 - {improving || checking ? (
170 - <div className="flex h-49 flex-col items-center justify-center gap-4">
171 - <div className="h-12 w-12 text-design-main">
172 - {loaderThreeDots}
173 - </div>
174 - <p className="m-0 text-base leading-6 text-center text-gray-800">
175 - {checking &&
176 - __('Reviewing your description...', 'extendify-local')}
177 - {improving &&
178 - __('Enhancing the website description...', 'extendify-local')}
179 - </p>
180 - </div>
181 - ) : (
182 - <>
183 - <textarea
184 - ref={textareaRef}
185 - id="extendify-launch-chat-textarea"
186 - className="flex min-h-20 md:min-h-24 w-full resize-none bg-transparent text-base leading-6 placeholder:text-gray-700 focus:shadow-none focus:outline-hidden border-none text-gray-900 p-6 pb-0"
187 - rows="1"
188 - // biome-ignore lint: Allow autofocus here
189 - autoFocus={!showTitle}
190 - autoComplete="off"
191 - data-1p-ignore
192 - value={input}
193 - onChange={(e) => {
194 - setInput(e.target.value);
195 - }}
196 - placeholder={placeholder}
197 - />
198 - <div className="flex justify-between items-end gap-4 p-6">
199 - <div>
200 - <EnhanceWithAIButton
201 - disabled={
202 - input.trim().length === 0 || input.trim() === lastImproved
203 - }
204 - onClick={handleImprove}
205 - />
206 - </div>
207 - <InlineSubmitButton disabled={submitDisabled} />
208 - </div>
209 - </>
210 - )}
211 - </div>
212 - <OutsideSubmitButton
213 - disabled={submitDisabled}
214 - improving={improving || checking}
215 - />
216 - </form>
217 - <div
218 - className="text-pretty mt-4 text-center text-xs leading-4 opacity-70 text-banner-text [&>a]:text-xs [&>a]:text-banner-text [&>a]:underline w-full"
219 - dangerouslySetInnerHTML={{ __html: consentTerms }}
220 - />
221 - </>
222 - );
223 -};
224 -
225 -const useDescriptionPlaceholder = () =>
226 - getAbTest('AutoLaunch.DescriptionPlaceholderLaw').variant === 'B'
227 - ? __(
228 - 'E.g., A boutique law firm specializing in family law, estate planning, and real estate, offering trusted, personalized counsel to clients across the region.',
229 - 'extendify-local',
230 - )
231 - : __(
232 - 'E.g., A personal photography portfolio featuring a collection of landscape, portrait, and street photography, capturing moments from around the world.',
233 - 'extendify-local',
234 - );
235 -
236 -const TitleField = ({ title, setTitle, setData, improving }) => {
237 - if (!getShowTitle() || improving) return null;
238 - return (
239 - <>
240 - <div className="mb-4 w-full">
241 - <label
242 - htmlFor="extendify-launch-site-title"
243 - className="mb-2 block px-2 text-base font-medium leading-6 text-banner-text"
244 - >
245 - {__('Website title (required)', 'extendify-local')}
246 - </label>
247 - <div className="w-full rounded-3xl border border-gray-300 bg-gray-100/80 text-gray-900 backdrop-blur-2xl focus-within:border-gray-500 focus-within:ring-gray-500 shadow-md overflow-hidden">
248 - <input
249 - id="extendify-launch-site-title"
250 - type="text"
251 - className="w-full bg-transparent text-base font-medium leading-6 placeholder:text-gray-700 placeholder:font-normal focus:shadow-none focus:outline-hidden border-none text-gray-900 px-6 py-4"
252 - // biome-ignore lint: Allow autofocus here
253 - autoFocus
254 - autoComplete="off"
255 - data-1p-ignore
256 - value={title}
257 - // the form's onClick refocuses the textarea; keep clicks here local
258 - onClick={(e) => e.stopPropagation()}
259 - onChange={(e) => {
260 - setTitle(e.target.value);
261 - setData('title', e.target.value);
262 - }}
263 - placeholder={__('Enter your website name', 'extendify-local')}
264 - />
265 - </div>
266 - </div>
267 - <label
268 - htmlFor="extendify-launch-chat-textarea"
269 - className="mb-2 block px-2 text-base font-medium leading-6 text-banner-text"
270 - >
271 - {__('Describe your website', 'extendify-local')}
272 - </label>
273 - </>
274 - );
275 -};
276 -
277 -const InlineSubmitButton = ({ disabled }) => {
278 - if (getSubmitOutside()) return null;
279 - return <SubmitButton disabled={disabled} />;
280 -};
281 -
282 -const OutsideSubmitButton = ({ disabled, improving }) => {
283 - if (!getSubmitOutside() || improving) {
284 - return null;
285 - }
286 - return (
287 - <div className="mt-4 flex justify-end">
288 - <SubmitButton disabled={disabled} />
289 - </div>
290 - );
291 -};
292 -
293 -const SubmitButton = forwardRef((props, ref) => {
294 - const label =
295 - getAbTest('AutoLaunch.SubmitCreateWebsite').variant === 'B'
296 - ? __('Create website', 'extendify-local')
297 - : __('Next', 'extendify-local');
298 - return (
299 - <button
300 - ref={ref}
301 - type="submit"
302 - className="inline-flex items-center justify-center rounded-full border-0 bg-design-main px-3 py-2 text-sm leading-5 font-normal text-design-text focus-visible:ring-design-main disabled:opacity-40 focus:outline-none focus-visible:ring-1 focus-visible:ring-offset-2 group hover:opacity-90 transition-opacity"
303 - {...props}
304 - >
305 - <span className="px-1">{label}</span>
306 - <Icon fill="currentColor" icon={chevronRight} size={24} />
307 - </button>
308 - );
309 -});
310 -
311 -const EnhanceWithAIButton = (props) => {
312 - if (getAbTest('AutoLaunch.HideEnhanceAI').variant === 'B') return null;
313 - return (
314 - <button
315 - type="button"
316 - className="inline-flex items-center rounded-full ring-1 ring-gray-800 px-3 py-2 text-sm leading-5 font-normal text-gray-800 transition-colors hover:bg-gray-600/5 disabled:opacity-40"
317 - {...props}
318 - >
319 - <Icon icon={pencil} size={24} />
320 - {/* translators: "Enhance with AI" refers to improving the current input using AI. */}
321 - <span className="px-1">{__('Enhance with AI', 'extendify-local')}</span>
322 - </button>
323 - );
324 -};
4 +export const DescriptionGathering = ({ autoFocus }) => (
5 + <DescriptionForm {...useDescriptionForm()} autoFocus={autoFocus} />
6 +);