PATH:
home
/
bnathsfovv
/
marcopaolini.info
/
wp-content
/
plugins
/
extendify
/
src
/
AutoLaunch
/
components
import { getExtendifyCodeRecommendation } from '@auto-launch/functions/extendify-code'; import { getAbTest } from '@auto-launch/functions/getAbTest'; import { fetchWithTimeout } from '@auto-launch/functions/helpers'; import { useInstallRequiredPlugins } from '@auto-launch/hooks/useInstallRequiredPlugins'; import { loaderThreeDots } from '@auto-launch/icons'; import { useLaunchDataStore } from '@auto-launch/state/launch-data'; import { AI_HOST } from '@constants'; import { reqDataBasics } from '@shared/lib/data'; import { useAIConsentStore } from '@shared/state/ai-consent'; import { forwardRef, useCallback, useEffect, useRef, useState, } from '@wordpress/element'; import { decodeEntities } from '@wordpress/html-entities'; import { __ } from '@wordpress/i18n'; import { chevronRight, Icon, pencil } from '@wordpress/icons'; import { isURL } from '@wordpress/url'; const getShowTitle = () => getAbTest('AutoLaunch.ShowTitle').variant === 'B'; export const DescriptionGathering = () => { const { setData, descriptionBackup, urlParams } = useLaunchDataStore(); useInstallRequiredPlugins(); const [input, setInput] = useState( urlParams.description || urlParams.title || descriptionBackup || '', ); const blogname = window.extSharedData?.siteTitle || ''; const titlePrefill = !blogname || isURL(blogname) ? '' : decodeEntities(blogname); const [title, setTitle] = useState(urlParams.title || titlePrefill); const [improving, setImproving] = useState(false); const [checking, setChecking] = useState(false); const [lastImproved, setLastImproved] = useState(null); const textareaRef = useRef(null); const { consentTerms } = useAIConsentStore(); // Showing the title field makes the description optional, so the submit // gate and the textarea autofocus both follow it. const showTitle = getShowTitle(); const submitDisabled = checking || (showTitle ? title.trim().length === 0 : input.trim().length === 0); const placeholder = useDescriptionPlaceholder(); // resize the height of the textarea based on the content const adjustHeight = useCallback(() => { const el = textareaRef.current; if (!el) return; const bottomPadding = 120; // tweak as needed // Reset to measure natural height el.style.height = 'auto'; const rect = el.getBoundingClientRect(); const viewportHeight = window.innerHeight; const maxAvailable = Math.max(0, viewportHeight - rect.top - bottomPadding); const desired = el.scrollHeight; const nextHeight = Math.min(desired, maxAvailable); el.style.height = `${nextHeight}px`; el.style.overflowY = desired > maxAvailable ? 'auto' : 'hidden'; // Notify others window.dispatchEvent(new Event('launch-textarea-resize')); }, []); const submitForm = async (e) => { e.preventDefault(); const trimmedTitle = title.trim(); const trimmedInput = input.trim(); if (showTitle) setData('title', trimmedTitle); setData('descriptionRaw', trimmedInput); // Only `showExtendifyCode` partners pay the classification latency; on a // `1` we divert to the connector screen instead of starting site creation. if (window.extSharedData?.showExtendifyCode) { setChecking(true); const recommend = await getExtendifyCodeRecommendation( trimmedInput || trimmedTitle, ); if (recommend === 1) { // Leave `checking` on so the loading state holds through the exit // transition instead of flashing the textarea before the connector. setData('showExtendifyCodeScreen', true); return; } setChecking(false); } setData('go', true); }; const handleImprove = async () => { setImproving(true); const url = `${AI_HOST}/api/prompt/improve`; const method = 'POST'; const headers = { 'Content-Type': 'application/json' }; const response = await fetchWithTimeout(url, { method, headers, body: JSON.stringify({ ...reqDataBasics, description: input.trim(), title: window.extSharedData.siteTitle, }), }) .then((res) => res.ok && res.json()) .catch(() => null); const nextValue = response?.improvedPrompt; setImproving(false); if (nextValue) { setLastImproved(nextValue); const el = textareaRef.current; if (!el) return setInput(nextValue); requestAnimationFrame(() => { // Preserve undo ability by using native events instead of React state el.focus(); el.select(); const ok = document.execCommand('insertText', false, nextValue); if (!ok) setInput(nextValue); }); } }; useEffect(() => { setData('descriptionBackup', input.trim()); const raf = requestAnimationFrame(() => { adjustHeight(); }); return () => cancelAnimationFrame(raf); }, [input, setData]); useEffect(() => { const controller = new AbortController(); const { signal } = controller; const handleResize = () => { adjustHeight(); const c = textareaRef.current; c?.scrollTo(0, c.scrollHeight); }; window.addEventListener('resize', handleResize, { signal }); window.addEventListener('orientationchange', handleResize, { signal }); adjustHeight(); return () => controller.abort(); }, [adjustHeight]); return ( <> {/* biome-ignore lint: allow onClick without keyboard */} <form onSubmit={submitForm} onClick={() => textareaRef.current?.focus()} className="relative flex w-full flex-col" > <TitleField title={title} setTitle={setTitle} setData={setData} improving={improving} /> <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"> {improving || checking ? ( <div className="flex h-49 flex-col items-center justify-center gap-4"> <div className="h-12 w-12 text-design-main"> {loaderThreeDots} </div> <p className="m-0 text-base leading-6 text-center text-gray-800"> {checking && __('Reviewing your description...', 'extendify-local')} {improving && __('Enhancing the website description...', 'extendify-local')} </p> </div> ) : ( <> <textarea ref={textareaRef} id="extendify-launch-chat-textarea" 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" rows="1" // biome-ignore lint: Allow autofocus here autoFocus={!showTitle} autoComplete="off" data-1p-ignore value={input} onChange={(e) => { setInput(e.target.value); }} placeholder={placeholder} /> <div className="flex justify-between items-end gap-4 p-6"> <div> <EnhanceWithAIButton disabled={ input.trim().length === 0 || input.trim() === lastImproved } onClick={handleImprove} /> </div> <InlineSubmitButton disabled={submitDisabled} /> </div> </> )} </div> <OutsideSubmitButton disabled={submitDisabled} improving={improving || checking} /> </form> <div 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" dangerouslySetInnerHTML={{ __html: consentTerms }} /> </> ); }; const useDescriptionPlaceholder = () => getAbTest('AutoLaunch.DescriptionPlaceholderLaw').variant === 'B' ? __( 'E.g., A boutique law firm specializing in family law, estate planning, and real estate, offering trusted, personalized counsel to clients across the region.', 'extendify-local', ) : __( 'E.g., A personal photography portfolio featuring a collection of landscape, portrait, and street photography, capturing moments from around the world.', 'extendify-local', ); const TitleField = ({ title, setTitle, setData, improving }) => { if (!getShowTitle() || improving) return null; return ( <> <div className="mb-4 w-full"> <label htmlFor="extendify-launch-site-title" className="mb-2 block px-2 text-base font-medium leading-6 text-banner-text" > {__('Website title (required)', 'extendify-local')} </label> <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"> <input id="extendify-launch-site-title" type="text" 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" // biome-ignore lint: Allow autofocus here autoFocus autoComplete="off" data-1p-ignore value={title} // the form's onClick refocuses the textarea; keep clicks here local onClick={(e) => e.stopPropagation()} onChange={(e) => { setTitle(e.target.value); setData('title', e.target.value); }} placeholder={__('Enter your website name', 'extendify-local')} /> </div> </div> <label htmlFor="extendify-launch-chat-textarea" className="mb-2 block px-2 text-base font-medium leading-6 text-banner-text" > {__('Describe your website', 'extendify-local')} </label> </> ); }; const InlineSubmitButton = ({ disabled }) => { if (getAbTest('AutoLaunch.SubmitOutside').variant === 'B') return null; return <SubmitButton disabled={disabled} />; }; const OutsideSubmitButton = ({ disabled, improving }) => { if (getAbTest('AutoLaunch.SubmitOutside').variant !== 'B' || improving) { return null; } return ( <div className="mt-4 flex justify-end"> <SubmitButton disabled={disabled} /> </div> ); }; const SubmitButton = forwardRef((props, ref) => { const label = getAbTest('AutoLaunch.SubmitCreateWebsite').variant === 'B' ? __('Create website', 'extendify-local') : __('Next', 'extendify-local'); return ( <button ref={ref} type="submit" 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" {...props} > <span className="px-1">{label}</span> <Icon fill="currentColor" icon={chevronRight} size={24} /> </button> ); }); const EnhanceWithAIButton = (props) => { if (getAbTest('AutoLaunch.HideEnhanceAI').variant === 'B') return null; return ( <button type="button" 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" {...props} > <Icon icon={pencil} size={24} /> {/* translators: "Enhance with AI" refers to improving the current input using AI. */} <span className="px-1">{__('Enhance with AI', 'extendify-local')}</span> </button> ); };
[-] MigrateChoice.jsx
[edit]
[-] Logo.jsx
[edit]
[-] MovingGradients.jsx
[edit]
[-] CreatingSite.jsx
[edit]
[-] ChoiceBox.jsx
[edit]
[-] DescriptionGathering.jsx
[edit]
[-] ExtendifyCodeConnector.jsx
[edit]
[-] ViewportPulse.jsx
[edit]
[-] Launch.jsx
[edit]
[-] NeedsTheme.jsx
[edit]
[-] RestartLaunchModal.jsx
[edit]
[+]
..