Preview: DescriptionGathering.jsx
Size: 6.47 KB
/home/nshryvcy/radiantskinclinics.org/wp-content/plugins/extendify_old/src/AutoLaunch/components/DescriptionGathering.jsx
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 { __ } from '@wordpress/i18n';
import { chevronRight, Icon, pencil } from '@wordpress/icons';
export const DescriptionGathering = () => {
const { setData, descriptionBackup, urlParams } = useLaunchDataStore();
useInstallRequiredPlugins();
const [input, setInput] = useState(
urlParams.description || urlParams.title || descriptionBackup || '',
);
const [improving, setImproving] = useState(false);
const [lastImproved, setLastImproved] = useState(null);
const textareaRef = useRef(null);
const { consentTerms } = useAIConsentStore();
// 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 = (e) => {
e.preventDefault();
setData('descriptionRaw', input.trim());
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"
>
<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 ? (
<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">
{__('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
value={input}
onChange={(e) => {
setInput(e.target.value);
}}
placeholder={__(
'E.g., A personal photography portfolio featuring a collection of landscape, portrait, and street photography, capturing moments from around the world.',
'extendify-local',
)}
/>
<div className="flex justify-between items-end gap-4 p-6">
<div>
<ImprovePrompt
disabled={
input.trim().length === 0 || input.trim() === lastImproved
}
onClick={handleImprove}
/>
</div>
<SubmitButton disabled={input.trim().length === 0} />
</div>
</>
)}
</div>
</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 SubmitButton = forwardRef((props, ref) => (
<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">{__('Next', 'extendify-local')}</span>
<Icon fill="currentColor" icon={chevronRight} size={24} />
</button>
));
const ImprovePrompt = (props) => {
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>
);
};
Directory Contents
Dirs: 0 × Files: 8