PluginProbe ʕ •ᴥ•ʔ
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI / 3.5.2
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI v3.5.2
3.5.2 3.5.1 3.5.0 3.4.8 3.4.7 3.4.6 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.5.1 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.10 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.6.1 1.6.7 1.7.0 1.7.0.1 1.7.0.2 1.7.0.3 1.7.1 1.7.2 1.7.2.1 1.7.2.2 1.7.3 1.7.4 1.7.5 1.7.5.1 1.7.5.2 1.7.6 1.7.7 1.7.7.1 1.7.7.2 1.7.8 1.7.9 1.8.0 1.8.0.1 1.8.1 1.8.2 1.8.2.1 1.8.2.2 1.8.2.3 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.0.1 1.9.1 1.9.2 1.9.3 1.9.4 1.9.4.1 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.0.1 2.0.1 2.0.2 2.0.3 2.0.3.1 2.0.4 2.0.4.1 2.0.5 2.0.6 2.0.7 2.0.8 2.0.8.1 2.0.9 3.0.0 3.0.0.1 3.0.1 3.0.2 3.0.3 3.0.3.1 3.0.4 3.0.4.1 3.0.4.2 3.0.5 3.0.5.1 3.0.5.2 3.0.6 3.0.6.1 3.0.7.1 3.0.8 3.0.8.1 3.0.9 3.0.9.1 3.0.9.2 3.0.9.3 3.0.9.4 3.0.9.5 3.1.0 3.1.1 3.1.2 3.2.0 3.2.1 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.3.0 3.4.0 3.4.1 3.4.2 3.4.2.1 3.4.3 3.4.4 3.4.5 trunk 1.0 1.0.1 1.0.2 1.0.3
everest-forms / src / templates / components / CreateWithAI.tsx
everest-forms / src / templates / components Last commit date
CreateFormCTA.tsx 2 weeks ago CreateWithAI.tsx 3 weeks ago Main.tsx 3 weeks ago PluginStatus.tsx 3 weeks ago Sidebar.tsx 3 weeks ago TemplateList.tsx 3 weeks ago TemplatesSkeleton.tsx 4 months ago
CreateWithAI.tsx
1794 lines
1 import {
2 Box,
3 Flex,
4 HStack,
5 Heading,
6 Icon,
7 Popover,
8 PopoverArrow,
9 PopoverBody,
10 PopoverContent,
11 PopoverTrigger,
12 SimpleGrid,
13 Spinner,
14 Text,
15 Textarea,
16 VStack,
17 keyframes,
18 useToast,
19 } from '@chakra-ui/react';
20 import apiFetch from '@wordpress/api-fetch';
21 import { __, sprintf } from '@wordpress/i18n';
22 import React, { useEffect, useRef, useState } from 'react';
23 import { BsStars } from 'react-icons/bs';
24 import {
25 FiArrowLeft,
26 FiArrowUp,
27 FiCheck,
28 FiEdit3,
29 FiRefreshCw,
30 } from 'react-icons/fi';
31 import { templatesScriptData } from '../utils/global';
32
33 const pulseGlow = keyframes`
34 0% { box-shadow: 0 0 0 0 rgba(117,69,187,0.3); transform: scale(1); }
35 50% { box-shadow: 0 0 28px 10px rgba(117,69,187,0.12); transform: scale(1.06); }
36 100% { box-shadow: 0 0 0 0 rgba(117,69,187,0.3); transform: scale(1); }
37 `;
38
39 const dotBounce = keyframes`
40 0%, 80%, 100% { transform: scale(0.35); opacity: 0.3; }
41 40% { transform: scale(1); opacity: 1; }
42 `;
43
44 const fadeUp = keyframes`
45 from { opacity: 0; transform: translateY(12px); }
46 to { opacity: 1; transform: translateY(0); }
47 `;
48
49 const shimmer = keyframes`
50 0% { background-position: -400px 0; }
51 100% { background-position: 400px 0; }
52 `;
53
54 const INSPIRATION_CARDS = [
55 {
56 title: __('Online store checkout', 'everest-forms'),
57 description: __(
58 'Product selection with prices, quantity, coupon code, order total, and payment gateway.',
59 'everest-forms',
60 ),
61 prompt:
62 'An online store checkout form with customer name and email, product selection as payment radio buttons with prices, quantity field, coupon code field, order subtotal display, order total display, shipping address, and a payment gateway section.',
63 },
64 {
65 title: __('Subscription sign-up', 'everest-forms'),
66 description: __(
67 'Subscription plan selection, billing details, and payment gateway for a recurring membership.',
68 'everest-forms',
69 ),
70 prompt:
71 'A subscription membership sign-up form with first name, last name, email, phone number, subscription plan selection with monthly and annual pricing options, billing address, and payment gateway fields.',
72 },
73 {
74 title: __('Event ticket purchase', 'everest-forms'),
75 description: __(
76 'Ticket type with pricing, quantity, coupon code, total, and payment gateway.',
77 'everest-forms',
78 ),
79 prompt:
80 'An event ticket purchase form with attendee name and email, ticket type as payment radio buttons with prices (general admission, VIP, student), ticket quantity field, coupon code field, order subtotal and total display, and payment gateway fields.',
81 },
82 {
83 title: __('Freelance contract & NDA', 'everest-forms'),
84 description: __(
85 'Client details, project scope, agreed rate, contract terms, and digital signature.',
86 'everest-forms',
87 ),
88 prompt:
89 'A freelance contract and NDA form with client name, company, email, project title text field, project scope textarea, deliverables textarea, agreed rate number field, project start date, contract terms and conditions textarea, and a digital signature field for client approval.',
90 },
91 {
92 title: __('Health & wellness check-in', 'everest-forms'),
93 description: __(
94 'Pain, energy, and stress levels via range sliders, symptoms checklist, and notes.',
95 'everest-forms',
96 ),
97 prompt:
98 'A daily health and wellness check-in form with patient name, date, pain level range slider (0 to 10), energy level range slider (0 to 10), stress level range slider (0 to 10), sleep hours number field, symptoms checkboxes (headache, fatigue, nausea, dizziness), and a notes textarea for additional observations.',
99 },
100 {
101 title: __('User account registration', 'everest-forms'),
102 description: __(
103 'Username, email, password with confirmation, profile photo, and preferences.',
104 'everest-forms',
105 ),
106 prompt:
107 'A user account registration form with first name, last name, username text field, email address, password field, confirm password field, profile photo file upload, date of birth, country dropdown, and a newsletter subscription checkbox.',
108 },
109 ];
110
111 const GEN_STEPS = [
112 __('Understanding your prompt', 'everest-forms'),
113 __('Designing field structure', 'everest-forms'),
114 __('Configuring validation & logic', 'everest-forms'),
115 __('Finalizing your form', 'everest-forms'),
116 ];
117
118 const MAX_CHARS = 500;
119
120 const UPGRADE_URL =
121 'https://everestforms.net/upgrade/?utm_source=evf-free&utm_medium=ai-form-builder&utm_campaign=ai-rate-limit&utm_content=Upgrade+to+Pro';
122
123 const SkeletonField: React.FC<{ delay?: string }> = ({ delay = '0s' }) => (
124 <Box sx={{ animation: `${fadeUp} 0.4s ease ${delay} both` }}>
125 <Box
126 h="12px"
127 w="30%"
128 mb="8px"
129 borderRadius="4px"
130 sx={{
131 background:
132 'linear-gradient(90deg, #eeeeef 25%, #e4e4e8 50%, #eeeeef 75%)',
133 backgroundSize: '400px 100%',
134 animation: `${shimmer} 1.6s ease-in-out infinite`,
135 }}
136 />
137 <Box
138 h="36px"
139 borderRadius="6px"
140 sx={{
141 background:
142 'linear-gradient(90deg, #eeeeef 25%, #e4e4e8 50%, #eeeeef 75%)',
143 backgroundSize: '400px 100%',
144 animation: `${shimmer} 1.6s ease-in-out infinite`,
145 }}
146 />
147 </Box>
148 );
149
150 const PageShell: React.FC<{
151 onBack: () => void;
152 backLabel?: string;
153 headerRight?: React.ReactNode;
154 children: React.ReactNode;
155 }> = ({ onBack, backLabel, headerRight, children }) => {
156 useEffect(() => {
157 const prev = document.body.style.overflow;
158 document.body.style.overflow = 'hidden';
159 return () => {
160 document.body.style.overflow = prev;
161 };
162 }, []);
163 return (
164 <Flex
165 position="fixed"
166 top="0"
167 left="0"
168 right="0"
169 bottom="0"
170 zIndex={100000}
171 direction="column"
172 overflow="hidden"
173 bg="#f6f6f8"
174 >
175 <Box h="4px" bg="#7545BB" flexShrink={0} />
176 <Flex
177 as="header"
178 align="center"
179 h="56px"
180 bg="white"
181 borderBottom="1px solid #e2e8f0"
182 px="24px"
183 flexShrink={0}
184 >
185 <HStack spacing="12px" flex="1">
186 <Box
187 as="button"
188 w="32px"
189 h="32px"
190 display="inline-flex"
191 alignItems="center"
192 justifyContent="center"
193 borderRadius="8px"
194 color="#383838"
195 bg="transparent"
196 border="none"
197 cursor="pointer"
198 onClick={onBack}
199 _hover={{ bg: '#f8fafc' }}
200 transition="background 0.2s"
201 >
202 <Icon as={FiArrowLeft} boxSize="4" />
203 </Box>
204 <Heading
205 as="h1"
206 fontSize="16px"
207 fontWeight="500"
208 color="#0e0e0e"
209 m="0"
210 >
211 {backLabel || __('Create with AI', 'everest-forms')}
212 </Heading>
213 </HStack>
214 {headerRight}
215 </Flex>
216 <Box flex={1} overflowY="auto" display="flex" flexDirection="column">
217 {children}
218 </Box>
219 </Flex>
220 );
221 };
222
223 interface CreateWithAIProps {
224 onBack: () => void;
225 initialFormId?: number;
226 initialTitle?: string;
227 }
228
229 interface ChatMessage {
230 role: 'user' | 'assistant';
231 text: string;
232 loading?: boolean;
233 error?: boolean;
234 notice?: boolean;
235 noticeUrl?: string;
236 }
237
238 const { restURL, security, ajaxUrl, aiNonce } = templatesScriptData;
239
240 const callAi = async (
241 action: string,
242 data: Record<string, string | number> = {},
243 ): Promise<any> => {
244 const body = new URLSearchParams();
245 body.append('action', action);
246 body.append('nonce', aiNonce);
247 Object.entries(data).forEach(([key, value]) =>
248 body.append(key, String(value)),
249 );
250
251 const response = await fetch(ajaxUrl, {
252 method: 'POST',
253 credentials: 'same-origin',
254 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
255 body: body.toString(),
256 });
257
258 return response.json();
259 };
260
261 const CreateWithAI: React.FC<CreateWithAIProps> = ({
262 onBack,
263 initialFormId,
264 initialTitle,
265 }) => {
266 const toast = useToast();
267 const [prompt, setPrompt] = useState('');
268 const [isRateLimited, setIsRateLimited] = useState(false);
269 const [genState, setGenState] = useState<'idle' | 'generating' | 'generated'>(
270 'idle',
271 );
272 const [genStep, setGenStep] = useState(-1);
273 const [hint, setHint] = useState({ show: false, x: 0, y: 0 });
274 const [isRegenerating, setIsRegenerating] = useState(false);
275 const [isCreatingForm, setIsCreatingForm] = useState(false);
276 const [previewHTML, setPreviewHTML] = useState('');
277 const [isPreviewLoading, setIsPreviewLoading] = useState(false);
278 const [formId, setFormId] = useState(0);
279 const [formTitle, setFormTitle] = useState(initialTitle || '');
280 const [editUrl, setEditUrl] = useState('');
281 const [multiPartSteps, setMultiPartSteps] = useState<string[]>([]);
282 const [activePartTab, setActivePartTab] = useState(0);
283 const [refinePrompt, setRefinePrompt] = useState('');
284 const [previewVersion, setPreviewVersion] = useState(0);
285 const [messages, setMessages] = useState<ChatMessage[]>([]);
286 const previewHintTimer = React.useRef<ReturnType<typeof setTimeout> | null>(
287 null,
288 );
289 const aiResponseRef = React.useRef<any>(null);
290 const promptInputRef = React.useRef<HTMLTextAreaElement | null>(null);
291 const canvasRef = useRef<HTMLDivElement | null>(null);
292 const previewHTMLRef = React.useRef('');
293 const previewFetchStartedRef = React.useRef(false);
294
295 useEffect(() => {
296 if (!canvasRef.current || multiPartSteps.length === 0) return;
297 const rows = canvasRef.current.querySelectorAll<HTMLElement>(
298 '.evf-admin-row[data-part-id]',
299 );
300 const target = activePartTab + 1;
301 rows.forEach((row) => {
302 const pid = parseInt(row.dataset.partId || '1', 10);
303 row.style.display = pid === target ? '' : 'none';
304 });
305 }, [activePartTab, previewHTML, multiPartSteps]);
306
307 useEffect(() => {
308 if (genState !== 'idle') return;
309 const t = setTimeout(() => promptInputRef.current?.focus(), 50);
310 return () => clearTimeout(t);
311 }, [genState]);
312
313 useEffect(() => {
314 if (typeof initialFormId !== 'number' || initialFormId <= 0) return;
315 setFormId(initialFormId);
316 setPrompt(initialTitle || __('this form', 'everest-forms'));
317 setMessages([
318 {
319 role: 'assistant',
320 text: initialTitle
321 ? /* translators: %s: template name. */
322 sprintf(
323 __(
324 'Loaded the “%s” template. Tell me what to change — add fields, reword labels, or anything else.',
325 'everest-forms',
326 ),
327 initialTitle,
328 )
329 : __('Loaded your form. Tell me what to change.', 'everest-forms'),
330 },
331 ]);
332 setGenState('generated');
333 // eslint-disable-next-line react-hooks/exhaustive-deps
334 }, [initialFormId]);
335
336 const handleUseThisForm = async () => {
337 if (isCreatingForm || !formId) return;
338 setIsCreatingForm(true);
339 try {
340 const res = await callAi('evf_ai_activate_form', { form_id: formId });
341
342 if (res?.success && res.data?.edit_url) {
343 window.location.href = res.data.edit_url;
344 } else if (editUrl) {
345 window.location.href = editUrl;
346 } else {
347 throw new Error(res?.data?.message || 'Unexpected response');
348 }
349 } catch (e: any) {
350 setIsCreatingForm(false);
351 toast({
352 title: __('Error', 'everest-forms'),
353 description:
354 e?.message ||
355 __('Could not open the form. Please try again.', 'everest-forms'),
356 status: 'error',
357 position: 'bottom',
358 duration: 5000,
359 isClosable: true,
360 variant: 'subtle',
361 });
362 }
363 };
364
365 const resolveLoading = (
366 text: string,
367 isError = false,
368 isNotice = false,
369 noticeUrl = '',
370 ) =>
371 setMessages((m) => {
372 const next = [...m];
373 for (let i = next.length - 1; i >= 0; i--) {
374 if (next[i].role === 'assistant' && next[i].loading) {
375 next[i] = {
376 role: 'assistant',
377 text,
378 error: isError,
379 notice: isNotice,
380 noticeUrl,
381 };
382 break;
383 }
384 }
385 return next;
386 });
387
388 const handleUpdate = async (refinePromptText: string = '') => {
389 if (isRegenerating || !formId || !prompt.trim()) return;
390 const refine = (refinePromptText || '').trim();
391 const userText = refine || __('Regenerate the form', 'everest-forms');
392
393 setMessages((m) => [
394 ...m,
395 { role: 'user', text: userText },
396 { role: 'assistant', text: '', loading: true },
397 ]);
398 setRefinePrompt('');
399 setIsRegenerating(true);
400 try {
401 const res = await callAi('evf_ai_update_form', {
402 form_id: formId,
403 prompt,
404 refine_prompt: refine,
405 });
406 if (res?.success) {
407 if (res.data?.form_title) setFormTitle(res.data.form_title);
408 if (res.data?.multi_part_steps) {
409 setMultiPartSteps(res.data.multi_part_steps);
410 setActivePartTab(0);
411 }
412 const notice = res.data?.notice || '';
413 const noticeUrl = res.data?.notice_url || '';
414 const doneText = refine
415 ? __(
416 "Done — I've updated your form. Check the preview on the right.",
417 'everest-forms',
418 )
419 : __("Here's a fresh version of your form.", 'everest-forms');
420
421 if (notice && !messages.some((m) => m.notice && m.text === notice)) {
422 setMessages((m) => {
423 const next = [...m];
424 for (let i = next.length - 1; i >= 0; i--) {
425 if (next[i].role === 'assistant' && next[i].loading) {
426 next[i] = { role: 'assistant', text: doneText };
427 break;
428 }
429 }
430 return [
431 ...next,
432 { role: 'assistant', text: notice, notice: true, noticeUrl },
433 ];
434 });
435 } else {
436 resolveLoading(doneText, false, false, '');
437 }
438 if (res.data?.preview_html) {
439 previewHTMLRef.current = res.data.preview_html;
440 setPreviewHTML(res.data.preview_html);
441 } else {
442 setPreviewVersion((v) => v + 1);
443 }
444 } else {
445 if (res?.data?.code === 'daily_limit_reached') {
446 setIsRateLimited(true);
447 }
448 throw new Error(
449 res?.data?.message ||
450 __('Could not update the form. Please try again.', 'everest-forms'),
451 );
452 }
453 } catch (e: any) {
454 const message =
455 e?.message ||
456 __('Could not update the form. Please try again.', 'everest-forms');
457 resolveLoading(message, true);
458 } finally {
459 setIsRegenerating(false);
460 }
461 };
462
463 const handleRegenerate = () => handleUpdate();
464
465 const handleFieldClick = (e: React.MouseEvent) => {
466 const { clientX, clientY } = e;
467 setHint({ show: true, x: clientX, y: clientY });
468 if (previewHintTimer.current) clearTimeout(previewHintTimer.current);
469 previewHintTimer.current = setTimeout(
470 () => setHint((h) => ({ ...h, show: false })),
471 2600,
472 );
473 };
474
475 const hasPrompt = prompt.trim().length > 0;
476
477 useEffect(() => {
478 if (genState !== 'generating') return;
479 setGenStep(-1);
480 aiResponseRef.current = null;
481 previewHTMLRef.current = '';
482 previewFetchStartedRef.current = false;
483 setPreviewHTML('');
484
485 let cancelled = false;
486 let intervalId: ReturnType<typeof setInterval> | null = null;
487
488 const showError = (message: string, isRateLimit = false) => {
489 if (cancelled) return;
490 if (intervalId) clearInterval(intervalId);
491 if (isRateLimit) setIsRateLimited(true);
492 setGenState('idle');
493 toast({
494 title: isRateLimit
495 ? __('Daily limit reached', 'everest-forms')
496 : __('AI generation failed', 'everest-forms'),
497 description: isRateLimit ? (
498 <Box>
499 <Text mb={1}>{message}</Text>
500 <Box
501 as="a"
502 href={UPGRADE_URL}
503 target="_blank"
504 rel="noopener noreferrer"
505 fontWeight="600"
506 textDecoration="underline"
507 _hover={{ opacity: 0.8 }}
508 >
509 {__('Upgrade to Pro →', 'everest-forms')}
510 </Box>
511 </Box>
512 ) : (
513 message
514 ),
515 status: 'error',
516 position: 'bottom',
517 duration: 5000,
518 isClosable: true,
519 variant: 'subtle',
520 });
521 };
522
523 callAi('evf_ai_generate_form', { prompt })
524 .then((res: any) => {
525 if (res?.success && res.data?.form_id) {
526 const html = res.data.preview_html || '';
527 if (html) {
528 previewHTMLRef.current = html;
529 previewFetchStartedRef.current = true;
530 setPreviewHTML(html);
531 setIsPreviewLoading(false);
532 }
533 aiResponseRef.current = {
534 ok: true,
535 formId: res.data.form_id,
536 formTitle: res.data.form_title || '',
537 editUrl: res.data.edit_url || '',
538 multiPartSteps: res.data.multi_part_steps || [],
539 notice: res.data.notice || '',
540 noticeUrl: res.data.notice_url || '',
541 };
542 } else {
543 showError(
544 res?.data?.message ||
545 __('Something went wrong. Please try again.', 'everest-forms'),
546 res?.data?.code === 'daily_limit_reached',
547 );
548 }
549 })
550 .catch(() => {
551 showError(
552 __(
553 'Could not reach the AI service. Please try again.',
554 'everest-forms',
555 ),
556 );
557 });
558
559 let step = -1;
560 intervalId = setInterval(() => {
561 step += 1;
562 setGenStep(step);
563 if (step >= GEN_STEPS.length - 1) {
564 if (intervalId) clearInterval(intervalId);
565 const finish = () => {
566 if (cancelled) return;
567 const result = aiResponseRef.current;
568 if (!result) {
569 setTimeout(finish, 200);
570 return;
571 }
572 if (result.ok) {
573 setFormId(result.formId);
574 setFormTitle(result.formTitle || '');
575 setEditUrl(result.editUrl);
576 setMultiPartSteps(result.multiPartSteps || []);
577 setActivePartTab(0);
578 setMessages([
579 { role: 'user', text: prompt },
580 {
581 role: 'assistant',
582 text:
583 result.notice ||
584 __(
585 'Here\'s your form! Review the preview on the right and click "Use This Form" when you\'re happy with it.',
586 'everest-forms',
587 ),
588 notice: !!result.notice,
589 noticeUrl: result.noticeUrl || '',
590 },
591 ]);
592 setGenState('generated');
593 }
594 };
595 setTimeout(finish, 700);
596 }
597 }, 950);
598
599 return () => {
600 cancelled = true;
601 if (intervalId) clearInterval(intervalId);
602 };
603 }, [genState]);
604
605 useEffect(() => {
606 if (genState !== 'generated' || !formId) return;
607 if (
608 previewVersion === 0 &&
609 (previewHTMLRef.current || previewFetchStartedRef.current)
610 )
611 return;
612 let cancelled = false;
613 setIsPreviewLoading(true);
614 apiFetch({
615 path: `${restURL}everest-forms/v1/templates/ai-preview`,
616 method: 'POST',
617 data: { form_id: formId },
618 headers: { 'X-WP-Nonce': security },
619 })
620 .then((res: any) => {
621 if (!cancelled && res?.success && res?.data?.html) {
622 previewHTMLRef.current = res.data.html;
623 setPreviewHTML(res.data.html);
624 }
625 })
626 .catch(() => {})
627 .finally(() => {
628 if (!cancelled) setIsPreviewLoading(false);
629 });
630 return () => {
631 cancelled = true;
632 };
633 }, [genState, formId, previewVersion]);
634
635 const handleGenerate = () => {
636 if (!hasPrompt) return;
637 setGenState('generating');
638 };
639
640 if (genState === 'generating') {
641 return (
642 <PageShell onBack={onBack}>
643 <Flex
644 flex="1"
645 overflow="hidden"
646 sx={{ animation: `${fadeUp} 0.3s ease` }}
647 >
648 <Flex
649 w="480px"
650 flexShrink={0}
651 bg="white"
652 borderRight="1px solid #e2e8f0"
653 direction="column"
654 align="center"
655 justify="center"
656 px="36px"
657 py="40px"
658 gap="0"
659 >
660 <Box
661 w="60px"
662 h="60px"
663 borderRadius="full"
664 background="linear-gradient(135deg, #9c6de8 0%, #7545BB 100%)"
665 display="flex"
666 alignItems="center"
667 justifyContent="center"
668 mb="20px"
669 sx={{ animation: `${pulseGlow} 2s ease-in-out infinite` }}
670 >
671 <Icon as={BsStars} boxSize={6} color="white" />
672 </Box>
673
674 <VStack spacing="4px" textAlign="center" mb="24px">
675 <Heading
676 as="h2"
677 fontSize="20px"
678 fontWeight="700"
679 color="#0e0e0e"
680 margin="0"
681 letterSpacing="-0.3px"
682 >
683 {__('Building your form…', 'everest-forms')}
684 </Heading>
685 <Text fontSize="13px" color="#9a9a9a" margin="0">
686 {__('This usually takes a few seconds', 'everest-forms')}
687 </Text>
688 </VStack>
689
690 <Box
691 bg="#faf9ff"
692 borderRadius="12px"
693 border="1px solid #ede8f8"
694 p="20px 24px"
695 width="100%"
696 mb="20px"
697 >
698 <VStack align="stretch" spacing="14px">
699 {GEN_STEPS.map((step, i) => {
700 const isDone = i < genStep;
701 const isActive = i === genStep;
702 const isPending = i > genStep;
703 return (
704 <HStack
705 key={i}
706 spacing="12px"
707 opacity={isPending ? 0.3 : 1}
708 transition="opacity 0.4s"
709 >
710 <Flex
711 w="20px"
712 h="20px"
713 borderRadius="full"
714 flexShrink={0}
715 bg={isDone ? '#7545BB' : 'transparent'}
716 border={isDone ? 'none' : '2px solid'}
717 borderColor={isActive ? '#7545BB' : '#ddd'}
718 align="center"
719 justify="center"
720 transition="all 0.35s"
721 >
722 {isDone && (
723 <Icon
724 as={FiCheck}
725 color="white"
726 sx={{ width: '9px', height: '9px', strokeWidth: 3 }}
727 />
728 )}
729 {isActive && (
730 <Box
731 w="6px"
732 h="6px"
733 borderRadius="full"
734 bg="#7545BB"
735 sx={{
736 animation: `${dotBounce} 1.1s ease-in-out infinite`,
737 }}
738 />
739 )}
740 </Flex>
741 <Text
742 flex={1}
743 fontSize="13px"
744 margin="0"
745 fontWeight={isActive ? '600' : isDone ? '500' : '400'}
746 color={
747 isActive ? '#7545BB' : isDone ? '#1a1a1a' : '#c0c0cc'
748 }
749 transition="all 0.35s"
750 >
751 {step}
752 </Text>
753 {isActive && (
754 <HStack spacing="3px">
755 {[0, 1, 2].map((d) => (
756 <Box
757 key={d}
758 w="4px"
759 h="4px"
760 borderRadius="full"
761 bg="#7545BB"
762 sx={{
763 animation: `${dotBounce} 1.1s ease-in-out ${d * 0.18}s infinite`,
764 }}
765 />
766 ))}
767 </HStack>
768 )}
769 </HStack>
770 );
771 })}
772 </VStack>
773 </Box>
774 </Flex>
775
776 <Box flex={1} bg="#f6f6f8" overflowY="auto" p="24px">
777 <Box
778 bg="white"
779 border="1px solid #e2e8f0"
780 borderRadius="16px"
781 overflow="hidden"
782 sx={{ animation: `${fadeUp} 0.4s ease 0.15s both` }}
783 >
784 <Flex
785 align="center"
786 px="24px"
787 py="16px"
788 borderBottom="1px solid #e2e8f0"
789 gap="10px"
790 >
791 <Box
792 w="32px"
793 h="32px"
794 borderRadius="8px"
795 sx={{
796 background:
797 'linear-gradient(90deg, #eeeeef 25%, #e4e4e8 50%, #eeeeef 75%)',
798 backgroundSize: '400px 100%',
799 animation: `${shimmer} 1.6s ease-in-out infinite`,
800 }}
801 />
802 <Box
803 h="14px"
804 w="40%"
805 borderRadius="4px"
806 sx={{
807 background:
808 'linear-gradient(90deg, #eeeeef 25%, #e4e4e8 50%, #eeeeef 75%)',
809 backgroundSize: '400px 100%',
810 animation: `${shimmer} 1.6s ease-in-out infinite`,
811 }}
812 />
813 </Flex>
814 <Box p="24px">
815 <VStack spacing="16px" align="stretch">
816 <SkeletonField delay="0.1s" />
817 <SkeletonField delay="0.22s" />
818 <SkeletonField delay="0.34s" />
819 <SkeletonField delay="0.46s" />
820 <SkeletonField delay="0.58s" />
821 </VStack>
822 </Box>
823 </Box>
824 </Box>
825 </Flex>
826 </PageShell>
827 );
828 }
829
830 if (genState === 'generated') {
831 let lastAssistantIdx = -1;
832 let useThisFormIdx = -1;
833 messages.forEach((m, i) => {
834 if (m.role === 'assistant' && !m.loading) {
835 lastAssistantIdx = i;
836 if (!m.error) useThisFormIdx = i;
837 }
838 });
839 return (
840 <PageShell
841 onBack={() => setGenState('idle')}
842 backLabel={__('New Prompt', 'everest-forms')}
843 headerRight={
844 <Box
845 as="button"
846 display="inline-flex"
847 alignItems="center"
848 gap="6px"
849 h="32px"
850 px="14px"
851 borderRadius="8px"
852 border="none"
853 bg={isCreatingForm ? '#9660db' : '#7545BB'}
854 color="white"
855 fontSize="13px"
856 fontWeight="600"
857 cursor={isCreatingForm ? 'not-allowed' : 'pointer'}
858 opacity={isCreatingForm ? 0.85 : 1}
859 onClick={handleUseThisForm}
860 _hover={{ bg: isCreatingForm ? '#9660db' : '#6a3daa' }}
861 transition="background 0.2s, opacity 0.2s"
862 >
863 {isCreatingForm && (
864 <Spinner size="xs" color="white" thickness="2px" speed="0.65s" />
865 )}
866 {isCreatingForm
867 ? __('Creating…', 'everest-forms')
868 : __('Use This Form', 'everest-forms')}
869 </Box>
870 }
871 >
872 {hint.show && (
873 <Box
874 position="fixed"
875 top={`${hint.y + 14}px`}
876 left={`${hint.x}px`}
877 transform="translateX(-50%)"
878 bg="rgba(18,18,18,0.93)"
879 color="white"
880 borderRadius="8px"
881 px="14px"
882 py="10px"
883 textAlign="center"
884 pointerEvents="none"
885 zIndex={9999}
886 boxShadow="0 4px 20px rgba(0,0,0,0.2)"
887 maxW="260px"
888 sx={{ animation: `${fadeUp} 0.15s ease` }}
889 >
890 <Box
891 position="absolute"
892 top="-5px"
893 left="50%"
894 transform="translateX(-50%)"
895 width="0"
896 height="0"
897 borderLeft="5px solid transparent"
898 borderRight="5px solid transparent"
899 borderBottom="5px solid rgba(18,18,18,0.93)"
900 />
901 <Text
902 fontSize="12px"
903 fontWeight="600"
904 margin="0 0 3px"
905 lineHeight="1.45"
906 >
907 {__('This is just a preview of your form.', 'everest-forms')}
908 </Text>
909 <Text
910 fontSize="12px"
911 color="rgba(255,255,255,0.65)"
912 margin="0"
913 lineHeight="1.45"
914 >
915 {__('Click "Use This Form" to start editing.', 'everest-forms')}
916 </Text>
917 </Box>
918 )}
919
920 <Flex
921 flex="1"
922 overflow="hidden"
923 sx={{ animation: `${fadeUp} 0.35s ease` }}
924 >
925 <Flex
926 w="480px"
927 flexShrink={0}
928 bg="white"
929 borderRight="1px solid #e2e8f0"
930 direction="column"
931 overflow="hidden"
932 >
933 <Box flex={1} p="20px" overflowY="auto">
934 {messages.map((msg, idx) => {
935 if (msg.role === 'user') {
936 return (
937 <Flex key={idx} justify="flex-end" mb="16px">
938 <Box
939 bg="#7545BB"
940 color="white"
941 borderRadius="16px 16px 4px 16px"
942 px="14px"
943 py="10px"
944 fontSize="14px"
945 lineHeight="1.6"
946 maxW="340px"
947 boxShadow="0 2px 8px rgba(117,69,187,0.2)"
948 >
949 {msg.text}
950 </Box>
951 </Flex>
952 );
953 }
954
955 const isLastAssistant = idx === lastAssistantIdx;
956 const isUseThisFormMsg = idx === useThisFormIdx;
957 return (
958 <HStack key={idx} align="flex-start" spacing="10px" mb="16px">
959 <Flex
960 w="28px"
961 h="28px"
962 borderRadius="full"
963 bg="rgba(117,69,187,0.1)"
964 align="center"
965 justify="center"
966 flexShrink={0}
967 mt="2px"
968 >
969 <Icon as={BsStars} boxSize="13px" color="#7545BB" />
970 </Flex>
971
972 <Box
973 flex={1}
974 bg={msg.error || msg.notice ? '#fff8f8' : '#faf9ff'}
975 border={
976 msg.error || msg.notice
977 ? '1px solid #fcd5d5'
978 : '1px solid #ede8f8'
979 }
980 borderRadius="4px 16px 16px 16px"
981 p="16px"
982 boxShadow={
983 msg.error || msg.notice
984 ? 'none'
985 : '0 2px 10px rgba(117,69,187,0.06)'
986 }
987 >
988 {msg.loading ? (
989 <HStack spacing="8px">
990 <Spinner
991 size="xs"
992 color="#7545BB"
993 thickness="2px"
994 speed="0.65s"
995 />
996 <Text
997 fontSize="14px"
998 color="#7545BB"
999 margin="0"
1000 fontWeight="500"
1001 >
1002 {__('Updating your form…', 'everest-forms')}
1003 </Text>
1004 </HStack>
1005 ) : (
1006 <>
1007 <Text
1008 fontSize="14px"
1009 color={msg.error || msg.notice ? '#c0392b' : '#444'}
1010 lineHeight="1.65"
1011 margin={
1012 isUseThisFormMsg
1013 ? '0 0 14px'
1014 : msg.noticeUrl
1015 ? '0 0 10px'
1016 : '0'
1017 }
1018 >
1019 {msg.text}
1020 </Text>
1021
1022 {msg.noticeUrl && (
1023 <Box
1024 as="a"
1025 href={msg.noticeUrl}
1026 target="_blank"
1027 rel="noopener noreferrer"
1028 display="inline-block"
1029 mb={isUseThisFormMsg ? '10px' : '0'}
1030 color="#7545BB"
1031 fontSize="12px"
1032 fontWeight="600"
1033 _hover={{ textDecoration: 'underline' }}
1034 >
1035 {__('Upgrade to Pro →', 'everest-forms')}
1036 </Box>
1037 )}
1038
1039 {isUseThisFormMsg && (
1040 <Box
1041 as="button"
1042 w="100%"
1043 display="flex"
1044 alignItems="center"
1045 justifyContent="center"
1046 gap="8px"
1047 h="36px"
1048 borderRadius="8px"
1049 bg={isCreatingForm ? '#9660db' : '#7545BB'}
1050 color="white"
1051 fontSize="13px"
1052 fontWeight="600"
1053 border="none"
1054 cursor={
1055 isCreatingForm ? 'not-allowed' : 'pointer'
1056 }
1057 mb="12px"
1058 opacity={isCreatingForm ? 0.85 : 1}
1059 onClick={handleUseThisForm}
1060 _hover={{
1061 bg: isCreatingForm ? '#9660db' : '#6a3daa',
1062 }}
1063 transition="background 0.2s, opacity 0.2s"
1064 >
1065 {isCreatingForm && (
1066 <Spinner
1067 size="xs"
1068 color="white"
1069 thickness="2px"
1070 speed="0.65s"
1071 />
1072 )}
1073 {isCreatingForm
1074 ? __('Creating…', 'everest-forms')
1075 : __('Use This Form', 'everest-forms')}
1076 </Box>
1077 )}
1078
1079 <HStack justify="flex-end" pt="2px">
1080 {isRateLimited ? (
1081 <Popover trigger="hover" placement="top" isLazy>
1082 <PopoverTrigger>
1083 <HStack
1084 spacing="5px"
1085 cursor="not-allowed"
1086 opacity={0.5}
1087 >
1088 <Icon
1089 as={FiRefreshCw}
1090 boxSize="12px"
1091 color="#9ca3af"
1092 />
1093 <Text
1094 fontSize="12px"
1095 color="#9ca3af"
1096 margin="0"
1097 fontWeight="500"
1098 >
1099 {__('Redo', 'everest-forms')}
1100 </Text>
1101 </HStack>
1102 </PopoverTrigger>
1103 <PopoverContent
1104 w="auto"
1105 maxW="220px"
1106 zIndex={200000}
1107 _focus={{ outline: 'none' }}
1108 >
1109 <PopoverArrow />
1110 <PopoverBody p={3}>
1111 <Text
1112 fontSize="12px"
1113 color="#555"
1114 mb={2}
1115 lineHeight="1.5"
1116 >
1117 {__(
1118 "You've reached your daily free limit.",
1119 'everest-forms',
1120 )}
1121 </Text>
1122 <Box
1123 as="a"
1124 href={UPGRADE_URL}
1125 target="_blank"
1126 rel="noopener noreferrer"
1127 color="#7545BB"
1128 fontSize="12px"
1129 fontWeight="600"
1130 _hover={{ textDecoration: 'underline' }}
1131 >
1132 {__('Upgrade to Pro →', 'everest-forms')}
1133 </Box>
1134 </PopoverBody>
1135 </PopoverContent>
1136 </Popover>
1137 ) : (
1138 <HStack
1139 spacing="5px"
1140 cursor={
1141 isRegenerating ? 'not-allowed' : 'pointer'
1142 }
1143 opacity={isRegenerating ? 0.5 : 1}
1144 _hover={{
1145 opacity: isRegenerating ? 0.5 : 0.65,
1146 }}
1147 onClick={
1148 isRegenerating ? undefined : handleRegenerate
1149 }
1150 >
1151 {isRegenerating && isLastAssistant ? (
1152 <Spinner
1153 size="xs"
1154 color="#9ca3af"
1155 thickness="2px"
1156 speed="0.65s"
1157 />
1158 ) : (
1159 <Icon
1160 as={FiRefreshCw}
1161 boxSize="12px"
1162 color="#9ca3af"
1163 />
1164 )}
1165 <Text
1166 fontSize="12px"
1167 color="#9ca3af"
1168 margin="0"
1169 fontWeight="500"
1170 >
1171 {__('Redo', 'everest-forms')}
1172 </Text>
1173 </HStack>
1174 )}
1175 </HStack>
1176 </>
1177 )}
1178 </Box>
1179 </HStack>
1180 );
1181 })}
1182 </Box>
1183
1184 <Box borderTop="1px solid #e2e8f0" p="16px">
1185 <Box
1186 border="1px solid #e2e8f0"
1187 borderRadius="12px"
1188 bg="white"
1189 overflow="hidden"
1190 transition="border-color 0.2s"
1191 _focusWithin={{ borderColor: '#7545BB' }}
1192 >
1193 <Textarea
1194 placeholder={__('Refine or follow up…', 'everest-forms')}
1195 border="none"
1196 fontSize="14px"
1197 color="#333"
1198 _focus={{ boxShadow: 'none' }}
1199 p="12px 16px"
1200 minHeight="56px"
1201 resize="none"
1202 _placeholder={{ color: '#c0c0cc' }}
1203 value={refinePrompt}
1204 isDisabled={isRegenerating}
1205 onChange={(e) =>
1206 setRefinePrompt(e.target.value.slice(0, MAX_CHARS))
1207 }
1208 onKeyDown={(e) => {
1209 if (e.key === 'Enter' && !e.shiftKey && !isRateLimited) {
1210 e.preventDefault();
1211 handleUpdate(refinePrompt);
1212 }
1213 }}
1214 />
1215 <Flex justify="flex-end" px="12px" pb="10px">
1216 {isRateLimited ? (
1217 <Popover trigger="hover" placement="top" isLazy>
1218 <PopoverTrigger>
1219 <Box
1220 as="button"
1221 w="28px"
1222 h="28px"
1223 bg="#c9bce4"
1224 borderRadius="6px"
1225 display="inline-flex"
1226 alignItems="center"
1227 justifyContent="center"
1228 border="none"
1229 cursor="not-allowed"
1230 transition="background 0.2s"
1231 >
1232 <Icon as={FiArrowUp} boxSize="3.5" color="white" />
1233 </Box>
1234 </PopoverTrigger>
1235 <PopoverContent
1236 w="auto"
1237 maxW="220px"
1238 zIndex={200000}
1239 _focus={{ outline: 'none' }}
1240 >
1241 <PopoverArrow />
1242 <PopoverBody p={3}>
1243 <Text
1244 fontSize="12px"
1245 color="#555"
1246 mb={2}
1247 lineHeight="1.5"
1248 >
1249 {__(
1250 "You've reached your daily free limit.",
1251 'everest-forms',
1252 )}
1253 </Text>
1254 <Box
1255 as="a"
1256 href={UPGRADE_URL}
1257 target="_blank"
1258 rel="noopener noreferrer"
1259 color="#7545BB"
1260 fontSize="12px"
1261 fontWeight="600"
1262 _hover={{ textDecoration: 'underline' }}
1263 >
1264 {__('Upgrade to Pro →', 'everest-forms')}
1265 </Box>
1266 </PopoverBody>
1267 </PopoverContent>
1268 </Popover>
1269 ) : (
1270 <Box
1271 as="button"
1272 w="28px"
1273 h="28px"
1274 bg={
1275 refinePrompt.trim() && !isRegenerating
1276 ? '#7545BB'
1277 : '#c9bce4'
1278 }
1279 borderRadius="6px"
1280 display="inline-flex"
1281 alignItems="center"
1282 justifyContent="center"
1283 border="none"
1284 cursor={
1285 refinePrompt.trim() && !isRegenerating
1286 ? 'pointer'
1287 : 'not-allowed'
1288 }
1289 _hover={{
1290 bg:
1291 refinePrompt.trim() && !isRegenerating
1292 ? '#6a3daa'
1293 : '#c9bce4',
1294 }}
1295 transition="background 0.2s"
1296 onClick={() => handleUpdate(refinePrompt)}
1297 >
1298 {isRegenerating ? (
1299 <Spinner
1300 size="xs"
1301 color="white"
1302 thickness="2px"
1303 speed="0.65s"
1304 />
1305 ) : (
1306 <Icon as={FiArrowUp} boxSize="3.5" color="white" />
1307 )}
1308 </Box>
1309 )}
1310 </Flex>
1311 </Box>
1312 </Box>
1313 </Flex>
1314
1315 <Box
1316 flex={1}
1317 bg="#f6f6f8"
1318 display="flex"
1319 flexDirection="column"
1320 overflow="hidden"
1321 opacity={isRegenerating ? 0.5 : 1}
1322 transition="opacity 0.3s"
1323 >
1324 <Box
1325 flex={1}
1326 overflowY="auto"
1327 p="24px"
1328 pb={multiPartSteps.length > 0 ? '0' : '24px'}
1329 >
1330 <Box
1331 bg="white"
1332 border="1px solid #e2e8f0"
1333 borderRadius={
1334 multiPartSteps.length > 0 ? '16px 16px 0 0' : '16px'
1335 }
1336 borderBottom={
1337 multiPartSteps.length > 0 ? 'none' : '1px solid #e2e8f0'
1338 }
1339 overflow="hidden"
1340 mb="0"
1341 position="relative"
1342 transition="border-color 0.3s"
1343 >
1344 <Flex
1345 align="center"
1346 px="24px"
1347 py="16px"
1348 borderBottom="1px solid #e2e8f0"
1349 justify="space-between"
1350 >
1351 <HStack spacing="10px">
1352 <Box
1353 w="32px"
1354 h="32px"
1355 bg="rgba(117,69,187,0.1)"
1356 borderRadius="8px"
1357 display="flex"
1358 alignItems="center"
1359 justifyContent="center"
1360 >
1361 <Icon as={FiEdit3} boxSize="15px" color="#7545BB" />
1362 </Box>
1363 <Text
1364 fontSize="16px"
1365 fontWeight="600"
1366 color="#0e0e0e"
1367 margin="0"
1368 >
1369 {formTitle || __('AI Generated Form', 'everest-forms')}
1370 </Text>
1371 </HStack>
1372 {isRegenerating && (
1373 <HStack
1374 spacing="5px"
1375 sx={{ animation: `${fadeUp} 0.2s ease` }}
1376 >
1377 {[0, 1, 2].map((d) => (
1378 <Box
1379 key={d}
1380 w="5px"
1381 h="5px"
1382 borderRadius="full"
1383 bg="#7545BB"
1384 sx={{
1385 animation: `${dotBounce} 1.1s ease-in-out ${d * 0.2}s infinite`,
1386 }}
1387 />
1388 ))}
1389 <Text
1390 fontSize="11px"
1391 color="#7545BB"
1392 margin="0"
1393 fontWeight="500"
1394 >
1395 {__('Regenerating', 'everest-forms')}
1396 </Text>
1397 </HStack>
1398 )}
1399 </Flex>
1400
1401 <style
1402 dangerouslySetInnerHTML={{
1403 __html: `
1404 .evf-ai-preview-canvas .everest-forms-panel-content {
1405 height: auto !important;
1406 overflow: visible !important;
1407 }
1408 `,
1409 }}
1410 />
1411 {multiPartSteps.length > 0 && (
1412 <style
1413 dangerouslySetInnerHTML={{
1414 __html: `
1415 .evf-ai-preview-canvas .evf-admin-row[data-part-id] { display: none !important; }
1416 .evf-ai-preview-canvas .evf-admin-row[data-part-id="${activePartTab + 1}"] { display: flex !important; margin-bottom: 15px !important; }
1417 `,
1418 }}
1419 />
1420 )}
1421 <Box
1422 p="0"
1423 pointerEvents={
1424 isRegenerating || isCreatingForm ? 'none' : 'auto'
1425 }
1426 >
1427 {previewHTML ? (
1428 <div
1429 ref={canvasRef}
1430 className="evf-ai-preview-canvas"
1431 onClick={(e) => handleFieldClick(e)}
1432 dangerouslySetInnerHTML={{ __html: previewHTML }}
1433 />
1434 ) : (
1435 <Box p="24px">
1436 <VStack spacing="20px" align="stretch">
1437 {Array.from({ length: 5 }).map((_, idx) => (
1438 <SkeletonField key={idx} delay={`${idx * 0.1}s`} />
1439 ))}
1440 </VStack>
1441 </Box>
1442 )}
1443 </Box>
1444 </Box>
1445 </Box>
1446
1447 {multiPartSteps.length > 0 && (
1448 <Box
1449 bg="#fafafa"
1450 borderTop="1px solid #d5d9e2"
1451 borderLeft="1px solid #d5d9e2"
1452 borderRight="1px solid #d5d9e2"
1453 borderBottom="1px solid #d5d9e2"
1454 borderRadius="0 0 16px 16px"
1455 display="flex"
1456 alignItems="stretch"
1457 h="42px"
1458 mx="24px"
1459 mb="24px"
1460 overflow="hidden"
1461 flexShrink={0}
1462 pointerEvents={
1463 isRegenerating || isCreatingForm ? 'none' : 'auto'
1464 }
1465 >
1466 <Box
1467 as="ul"
1468 display="flex"
1469 listStyleType="none"
1470 m="0"
1471 p="0"
1472 flex="1"
1473 h="41px"
1474 alignSelf="flex-start"
1475 overflow="hidden"
1476 borderBottom="1px solid #cccccc"
1477 >
1478 {multiPartSteps.map((step, idx) => (
1479 <Box
1480 as="li"
1481 key={idx}
1482 display="block"
1483 flexShrink={0}
1484 h="40px"
1485 bg={activePartTab === idx ? '#eeeeee' : '#f7f7f7'}
1486 borderRadius={
1487 idx === 0
1488 ? '0'
1489 : idx === multiPartSteps.length - 1
1490 ? '0 0 16px 0'
1491 : '0'
1492 }
1493 cursor="pointer"
1494 onClick={() => setActivePartTab(idx)}
1495 >
1496 <Box
1497 as="a"
1498 href="#"
1499 onClick={(e: React.MouseEvent) => e.preventDefault()}
1500 display="inline-flex"
1501 alignItems="center"
1502 h="40px"
1503 px="10px"
1504 fontSize="13px"
1505 fontWeight="600"
1506 lineHeight="1"
1507 color={activePartTab === idx ? '#7e3bd0' : '#555555'}
1508 textDecoration="none"
1509 _hover={{
1510 color: activePartTab === idx ? '#7e3bd0' : '#333333',
1511 textDecoration: 'none',
1512 }}
1513 >
1514 <Text
1515 margin="0"
1516 fontSize="13px"
1517 fontWeight="600"
1518 color="inherit"
1519 >
1520 {step}
1521 </Text>
1522 </Box>
1523 </Box>
1524 ))}
1525 </Box>
1526 </Box>
1527 )}
1528 </Box>
1529 </Flex>
1530 </PageShell>
1531 );
1532 }
1533
1534 return (
1535 <PageShell onBack={onBack}>
1536 <Flex
1537 flex="1"
1538 direction="column"
1539 align="center"
1540 justify="center"
1541 px="24px"
1542 py="16px"
1543 >
1544 <Box width="100%" maxW="896px">
1545 <VStack spacing="8px" mb="14px" textAlign="center">
1546 <Heading
1547 as="h1"
1548 fontSize={{ base: '24px', md: '32px' }}
1549 fontWeight="600"
1550 color="#0e0e0e"
1551 lineHeight="1.15"
1552 m="0"
1553 letterSpacing="-0.02em"
1554 >
1555 {__('What should we build today?', 'everest-forms')}
1556 </Heading>
1557 <Text fontSize="15px" color="#6b6b6b" m="0" lineHeight="1.55">
1558 {__(
1559 "Describe your form in your own words. We'll handle the fields, logic and layout — in seconds.",
1560 'everest-forms',
1561 )}
1562 </Text>
1563 </VStack>
1564
1565 <Box
1566 borderRadius="16px"
1567 border="1px solid #e2e8f0"
1568 bg="white"
1569 boxShadow="0 1px 2px rgba(15,15,15,0.04)"
1570 mb="8px"
1571 >
1572 <Flex align="flex-start" gap="12px" px="20px" pt="20px">
1573 <Icon
1574 as={BsStars}
1575 boxSize="5"
1576 color="#c5c0d0"
1577 mt="2px"
1578 flexShrink={0}
1579 />
1580 <Textarea
1581 ref={promptInputRef}
1582 autoFocus
1583 value={prompt}
1584 onChange={(e) => setPrompt(e.target.value.slice(0, MAX_CHARS))}
1585 placeholder={__(
1586 'A feedback form to learn how onboarding felt, with a 1–5 rating and one optional comment…',
1587 'everest-forms',
1588 )}
1589 border="none"
1590 resize="none"
1591 fontSize="15px"
1592 lineHeight="1.6"
1593 color="#181818"
1594 p="0"
1595 minH="90px"
1596 bg="transparent"
1597 _focus={{ boxShadow: 'none', outline: 'none', border: 'none' }}
1598 _placeholder={{ color: '#9a9a9a' }}
1599 />
1600 </Flex>
1601
1602 <Box mx="20px" borderTop="1px solid #f1f5f9" />
1603
1604 <Flex align="center" justify="flex-end" px="16px" py="12px">
1605 {isRateLimited ? (
1606 <Popover trigger="hover" placement="top" isLazy>
1607 <PopoverTrigger>
1608 <Box
1609 as="button"
1610 display="inline-flex"
1611 alignItems="center"
1612 gap="8px"
1613 fontSize="14px"
1614 fontWeight="500"
1615 pl="16px"
1616 pr="8px"
1617 py="8px"
1618 borderRadius="8px"
1619 bg="#e6e3ee"
1620 color="#9a9a9a"
1621 cursor="not-allowed"
1622 border="none"
1623 transition="background 0.2s"
1624 >
1625 <Text margin="0" color="#9a9a9a">
1626 {__('Generate', 'everest-forms')}
1627 </Text>
1628 <Box
1629 w="24px"
1630 h="24px"
1631 borderRadius="6px"
1632 display="inline-flex"
1633 alignItems="center"
1634 justifyContent="center"
1635 bg="#d5d0e0"
1636 >
1637 <Icon as={FiArrowUp} boxSize="3.5" color="#9a9a9a" />
1638 </Box>
1639 </Box>
1640 </PopoverTrigger>
1641 <PopoverContent
1642 w="auto"
1643 maxW="220px"
1644 zIndex={200000}
1645 _focus={{ outline: 'none' }}
1646 >
1647 <PopoverArrow />
1648 <PopoverBody p={3}>
1649 <Text
1650 fontSize="12px"
1651 color="#555"
1652 mb={2}
1653 lineHeight="1.5"
1654 >
1655 {__(
1656 "You've reached your daily free limit.",
1657 'everest-forms',
1658 )}
1659 </Text>
1660 <Box
1661 as="a"
1662 href={UPGRADE_URL}
1663 target="_blank"
1664 rel="noopener noreferrer"
1665 color="#7545BB"
1666 fontSize="12px"
1667 fontWeight="600"
1668 _hover={{ textDecoration: 'underline' }}
1669 >
1670 {__('Upgrade to Pro →', 'everest-forms')}
1671 </Box>
1672 </PopoverBody>
1673 </PopoverContent>
1674 </Popover>
1675 ) : (
1676 <Box
1677 as="button"
1678 display="inline-flex"
1679 alignItems="center"
1680 gap="8px"
1681 fontSize="14px"
1682 fontWeight="500"
1683 pl="16px"
1684 pr="8px"
1685 py="8px"
1686 borderRadius="8px"
1687 bg={hasPrompt ? '#7545BB' : '#e6e3ee'}
1688 color={hasPrompt ? 'white' : '#9a9a9a'}
1689 cursor={hasPrompt ? 'pointer' : 'not-allowed'}
1690 border="none"
1691 onClick={hasPrompt ? handleGenerate : undefined}
1692 _hover={hasPrompt ? { bg: '#6a3daa' } : {}}
1693 transition="background 0.2s"
1694 >
1695 <Text margin="0" color={hasPrompt ? 'white' : '#9a9a9a'}>
1696 {__('Generate', 'everest-forms')}
1697 </Text>
1698 <Box
1699 w="24px"
1700 h="24px"
1701 borderRadius="6px"
1702 display="inline-flex"
1703 alignItems="center"
1704 justifyContent="center"
1705 bg={hasPrompt ? '#6a3daa' : '#d5d0e0'}
1706 >
1707 <Icon
1708 as={FiArrowUp}
1709 boxSize="3.5"
1710 color={hasPrompt ? 'white' : '#9a9a9a'}
1711 />
1712 </Box>
1713 </Box>
1714 )}
1715 </Flex>
1716 </Box>
1717
1718 <Text
1719 textAlign="right"
1720 fontSize="11px"
1721 color="#9a9a9a"
1722 m="0"
1723 pr="4px"
1724 mb="14px"
1725 >
1726 {prompt.length}/{MAX_CHARS}
1727 </Text>
1728
1729 <Box>
1730 <Flex align="flex-end" justify="space-between" mb="10px">
1731 <Heading
1732 as="h2"
1733 fontSize="20px"
1734 fontWeight="600"
1735 color="#0e0e0e"
1736 m="0"
1737 letterSpacing="-0.01em"
1738 >
1739 {__('Need inspiration?', 'everest-forms')}
1740 </Heading>
1741 <Text fontSize="13px" fontWeight="500" color="#9a9a9a" m="0">
1742 {__('Tap a card to use as starting point', 'everest-forms')}
1743 </Text>
1744 </Flex>
1745
1746 <SimpleGrid columns={{ base: 1, sm: 2 }} spacing="8px">
1747 {INSPIRATION_CARDS.map((card) => (
1748 <Box
1749 key={card.title}
1750 as="button"
1751 textAlign="left"
1752 display="block"
1753 width="100%"
1754 border="1px solid rgba(226,232,240,0.8)"
1755 bg="white"
1756 px="16px"
1757 py="12px"
1758 borderRadius="8px"
1759 cursor="pointer"
1760 onClick={() => setPrompt(card.prompt)}
1761 _hover={{
1762 borderColor: 'rgba(117,69,187,0.4)',
1763 bg: 'rgba(117,69,187,0.02)',
1764 }}
1765 >
1766 <Text
1767 fontSize="14px"
1768 fontWeight="600"
1769 color="#0e0e0e"
1770 m="0 0 4px"
1771 lineHeight="1.4"
1772 >
1773 {card.title}
1774 </Text>
1775 <Text
1776 fontSize="12.5px"
1777 color="#6b6b6b"
1778 m="0"
1779 lineHeight="1.4"
1780 >
1781 {card.description}
1782 </Text>
1783 </Box>
1784 ))}
1785 </SimpleGrid>
1786 </Box>
1787 </Box>
1788 </Flex>
1789 </PageShell>
1790 );
1791 };
1792
1793 export default CreateWithAI;
1794