CreateFormCTA.tsx
1 month ago
CreateWithAI.tsx
1 month ago
Main.tsx
1 month ago
PluginStatus.tsx
1 month ago
Sidebar.tsx
1 month ago
TemplateList.tsx
1 month ago
TemplatesSkeleton.tsx
5 months ago
TemplateList.tsx
894 lines
| 1 | import { |
| 2 | Box, |
| 3 | Button, |
| 4 | Flex, |
| 5 | HStack, |
| 6 | Icon, |
| 7 | Image, |
| 8 | Modal, |
| 9 | ModalBody, |
| 10 | ModalCloseButton, |
| 11 | ModalContent, |
| 12 | ModalHeader, |
| 13 | ModalOverlay, |
| 14 | Text, |
| 15 | useDisclosure, |
| 16 | useToast, |
| 17 | VStack, |
| 18 | } from '@chakra-ui/react'; |
| 19 | import { useMutation, useQueryClient } from '@tanstack/react-query'; |
| 20 | import apiFetch from '@wordpress/api-fetch'; |
| 21 | import { __ } from '@wordpress/i18n'; |
| 22 | import React, { useEffect, useState } from 'react'; |
| 23 | import { FaHeart, FaRegHeart } from 'react-icons/fa'; |
| 24 | import { FiArrowRight } from 'react-icons/fi'; |
| 25 | import { LuSparkles } from 'react-icons/lu'; |
| 26 | import notFoundImage from '../images/not-found-image.png'; |
| 27 | import { templatesScriptData } from '../utils/global'; |
| 28 | import PluginStatus from './PluginStatus'; |
| 29 | |
| 30 | interface Template { |
| 31 | id: number; |
| 32 | title: string; |
| 33 | slug: string; |
| 34 | imageUrl: string; |
| 35 | description: string; |
| 36 | isPro: boolean; |
| 37 | preview_link?: string; |
| 38 | addons?: { [key: string]: string }; |
| 39 | categories?: string[]; |
| 40 | } |
| 41 | |
| 42 | interface TemplateListProps { |
| 43 | selectedCategory: string; |
| 44 | templates: Template[]; |
| 45 | onCreateWithAI?: (formId?: number, title?: string) => void; |
| 46 | } |
| 47 | |
| 48 | const { restURL, security } = templatesScriptData; |
| 49 | |
| 50 | // "Edit with AI" is disabled (shown greyed-out) on local / development sites where the AI gateway is unavailable. |
| 51 | const AI_ENABLED = !!templatesScriptData?.aiEnabled; |
| 52 | |
| 53 | interface CreateTemplateResponse { |
| 54 | success: boolean; |
| 55 | data?: { |
| 56 | id: number; |
| 57 | redirect: string; |
| 58 | status: number; |
| 59 | }; |
| 60 | message?: string; |
| 61 | } |
| 62 | |
| 63 | const TemplateList: React.FC<TemplateListProps> = ({ |
| 64 | selectedCategory, |
| 65 | templates, |
| 66 | onCreateWithAI, |
| 67 | }) => { |
| 68 | const [previewTemplate, setPreviewTemplate] = useState<Template | null>(null); |
| 69 | const [selectedTemplateSlug, setSelectedTemplateSlug] = useState<string>(''); |
| 70 | const [modalState, setModalState] = useState<'addons' | 'choose'>('choose'); |
| 71 | const { isOpen, onOpen, onClose } = useDisclosure(); |
| 72 | const [hoverCardId, setHoverCardId] = useState<number | null>(null); |
| 73 | const [favorites, setFavorites] = useState<string[]>([]); |
| 74 | const [isCreating, setIsCreating] = useState(false); |
| 75 | const toast = useToast(); |
| 76 | const queryClient = useQueryClient(); |
| 77 | const [isPluginModalOpen, setIsPluginModalOpen] = useState(false); |
| 78 | const [lockedTemplateName, setLockedTemplateName] = useState(''); |
| 79 | |
| 80 | const openPluginModal = () => setIsPluginModalOpen(true); |
| 81 | const closePluginModal = () => setIsPluginModalOpen(false); |
| 82 | |
| 83 | useEffect(() => { |
| 84 | const savedFavorites = localStorage.getItem('favorites'); |
| 85 | |
| 86 | if (savedFavorites) { |
| 87 | setFavorites(JSON.parse(savedFavorites)); |
| 88 | } else { |
| 89 | const fetchFavorites = async () => { |
| 90 | try { |
| 91 | const response: any = await apiFetch({ |
| 92 | path: `${restURL}everest-forms/v1/templates/favorite_forms`, |
| 93 | method: 'GET', |
| 94 | headers: { |
| 95 | 'X-WP-Nonce': security, |
| 96 | }, |
| 97 | }); |
| 98 | |
| 99 | if (response && Array.isArray(response)) { |
| 100 | setFavorites(response); |
| 101 | localStorage.setItem('favorites', JSON.stringify(response)); |
| 102 | } |
| 103 | } catch (error) { |
| 104 | console.error('Error fetching favorites:', error); |
| 105 | } |
| 106 | }; |
| 107 | |
| 108 | fetchFavorites(); |
| 109 | } |
| 110 | }, []); |
| 111 | |
| 112 | const handleTemplateClick = async (template: Template) => { |
| 113 | const addonKeys = template.addons ? Object.keys(template.addons) : []; |
| 114 | |
| 115 | try { |
| 116 | const response = await apiFetch({ |
| 117 | path: `${restURL}everest-forms/v1/plugin/upgrade`, |
| 118 | method: 'POST', |
| 119 | body: JSON.stringify({ requiredPlugins: addonKeys }), |
| 120 | headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': security }, |
| 121 | }); |
| 122 | |
| 123 | const { plugin_status } = response as { |
| 124 | plugin_status: Record<string, string>; |
| 125 | }; |
| 126 | |
| 127 | if (!plugin_status) { |
| 128 | setLockedTemplateName(template.title); |
| 129 | openPluginModal(); |
| 130 | return; |
| 131 | } |
| 132 | |
| 133 | setSelectedTemplateSlug(template.slug); |
| 134 | setPreviewTemplate(template); |
| 135 | |
| 136 | // No addons required → go straight to the choose state |
| 137 | if (addonKeys.length === 0) { |
| 138 | setModalState('choose'); |
| 139 | } else { |
| 140 | setModalState('addons'); |
| 141 | } |
| 142 | onOpen(); |
| 143 | } catch (error) { |
| 144 | toast({ |
| 145 | title: __('Error', 'everest-forms'), |
| 146 | description: __( |
| 147 | 'An error occurred while checking the plugin status. Please try again.', |
| 148 | 'everest-forms', |
| 149 | ), |
| 150 | status: 'error', |
| 151 | position: 'bottom-right', |
| 152 | duration: 5000, |
| 153 | isClosable: true, |
| 154 | variant: 'subtle', |
| 155 | }); |
| 156 | } |
| 157 | }; |
| 158 | |
| 159 | // Called when all addons are active — transition to the choose view |
| 160 | const handleAddonsReady = () => { |
| 161 | setModalState('choose'); |
| 162 | }; |
| 163 | |
| 164 | // "Edit with AI": create a DRAFT form from the template, then open the AI |
| 165 | // preview with it loaded so the user can refine it by prompting. |
| 166 | const [aiCreatingSlug, setAiCreatingSlug] = useState(''); |
| 167 | const handleEditWithAI = async (template: Template) => { |
| 168 | if (aiCreatingSlug) return; |
| 169 | setAiCreatingSlug(template.slug); |
| 170 | try { |
| 171 | const response = (await apiFetch({ |
| 172 | path: `${restURL}everest-forms/v1/templates/create`, |
| 173 | method: 'POST', |
| 174 | body: JSON.stringify({ |
| 175 | title: template.title, |
| 176 | slug: template.slug, |
| 177 | draft: true, |
| 178 | }), |
| 179 | headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': security }, |
| 180 | })) as CreateTemplateResponse; |
| 181 | |
| 182 | if (response.success && response.data?.id) { |
| 183 | // Navigate into the AI flow with the draft loaded (component unmounts). |
| 184 | onCreateWithAI?.(response.data.id, template.title); |
| 185 | } else { |
| 186 | throw new Error(response.message || 'create_failed'); |
| 187 | } |
| 188 | } catch (error) { |
| 189 | setAiCreatingSlug(''); |
| 190 | toast({ |
| 191 | title: __('Error', 'everest-forms'), |
| 192 | description: __( |
| 193 | 'Could not start AI editing. Please try again.', |
| 194 | 'everest-forms', |
| 195 | ), |
| 196 | status: 'error', |
| 197 | position: 'bottom-right', |
| 198 | duration: 5000, |
| 199 | isClosable: true, |
| 200 | variant: 'subtle', |
| 201 | }); |
| 202 | } |
| 203 | }; |
| 204 | |
| 205 | // Creates the form using the template title as name |
| 206 | const handleCreateForm = async () => { |
| 207 | if (!previewTemplate) return; |
| 208 | setIsCreating(true); |
| 209 | try { |
| 210 | const response = (await apiFetch({ |
| 211 | path: `${restURL}everest-forms/v1/templates/create`, |
| 212 | method: 'POST', |
| 213 | body: JSON.stringify({ |
| 214 | title: previewTemplate.title, |
| 215 | slug: selectedTemplateSlug, |
| 216 | }), |
| 217 | headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': security }, |
| 218 | })) as CreateTemplateResponse; |
| 219 | |
| 220 | if (response.success && response.data) { |
| 221 | window.location.href = response.data.redirect; |
| 222 | } else { |
| 223 | setIsCreating(false); |
| 224 | toast({ |
| 225 | title: __('Error', 'everest-forms'), |
| 226 | description: |
| 227 | response.message || |
| 228 | __('Failed to create form template.', 'everest-forms'), |
| 229 | status: 'error', |
| 230 | position: 'bottom-right', |
| 231 | duration: 5000, |
| 232 | isClosable: true, |
| 233 | variant: 'subtle', |
| 234 | }); |
| 235 | } |
| 236 | } catch (error) { |
| 237 | setIsCreating(false); |
| 238 | toast({ |
| 239 | title: __('Error', 'everest-forms'), |
| 240 | description: __( |
| 241 | 'An error occurred while creating the form template.', |
| 242 | 'everest-forms', |
| 243 | ), |
| 244 | status: 'error', |
| 245 | position: 'bottom-right', |
| 246 | duration: 5000, |
| 247 | isClosable: true, |
| 248 | variant: 'subtle', |
| 249 | }); |
| 250 | } |
| 251 | }; |
| 252 | |
| 253 | const mutation = useMutation( |
| 254 | async (slug: string) => { |
| 255 | const newFavorites = favorites.includes(slug) |
| 256 | ? favorites.filter((item) => item !== slug) |
| 257 | : [...favorites, slug]; |
| 258 | |
| 259 | setFavorites(newFavorites); |
| 260 | localStorage.setItem('favorites', JSON.stringify(newFavorites)); |
| 261 | |
| 262 | await apiFetch({ |
| 263 | path: `${restURL}everest-forms/v1/templates/favorite`, |
| 264 | method: 'POST', |
| 265 | body: JSON.stringify({ |
| 266 | action: newFavorites.includes(slug) |
| 267 | ? 'add_favorite' |
| 268 | : 'remove_favorite', |
| 269 | slug, |
| 270 | }), |
| 271 | headers: { |
| 272 | 'Content-Type': 'application/json', |
| 273 | 'X-WP-Nonce': security, |
| 274 | }, |
| 275 | }); |
| 276 | |
| 277 | return newFavorites; |
| 278 | }, |
| 279 | { |
| 280 | onError: () => { |
| 281 | toast({ |
| 282 | title: __('Error', 'everest-forms'), |
| 283 | description: __( |
| 284 | 'An error occurred while updating favorites.', |
| 285 | 'everest-forms', |
| 286 | ), |
| 287 | status: 'error', |
| 288 | position: 'bottom-right', |
| 289 | duration: 5000, |
| 290 | isClosable: true, |
| 291 | variant: 'subtle', |
| 292 | }); |
| 293 | }, |
| 294 | onSuccess: (newFavorites) => { |
| 295 | queryClient.invalidateQueries(['templates']); |
| 296 | setFavorites(newFavorites); |
| 297 | localStorage.setItem('favorites', JSON.stringify(newFavorites)); |
| 298 | queryClient.invalidateQueries(['favorites']); |
| 299 | }, |
| 300 | }, |
| 301 | ); |
| 302 | |
| 303 | const handleFavoriteToggle = (slug: string) => { |
| 304 | mutation.mutate(slug); |
| 305 | }; |
| 306 | |
| 307 | const requiredPlugins = previewTemplate?.addons |
| 308 | ? Object.entries(previewTemplate.addons).map(([key, value]) => ({ |
| 309 | key, |
| 310 | value, |
| 311 | })) |
| 312 | : []; |
| 313 | |
| 314 | return ( |
| 315 | <Box padding="0"> |
| 316 | {templates?.length ? ( |
| 317 | <Box |
| 318 | sx={{ |
| 319 | display: 'grid', |
| 320 | gridTemplateColumns: 'repeat(2, 1fr)', |
| 321 | gap: '16px', |
| 322 | '@media (min-width: 1280px)': { |
| 323 | gridTemplateColumns: 'repeat(3, 1fr)', |
| 324 | }, |
| 325 | '@media (max-width: 640px)': { |
| 326 | gridTemplateColumns: '1fr', |
| 327 | }, |
| 328 | }} |
| 329 | > |
| 330 | {templates.map((template) => { |
| 331 | const isHovered = hoverCardId === template.id; |
| 332 | return ( |
| 333 | <Box |
| 334 | key={template.slug} |
| 335 | borderRadius="12px" |
| 336 | border="1px solid #e2e8f0" |
| 337 | overflow="hidden" |
| 338 | position="relative" |
| 339 | onMouseEnter={() => setHoverCardId(template.id)} |
| 340 | onMouseLeave={() => setHoverCardId(null)} |
| 341 | bg="white" |
| 342 | display="flex" |
| 343 | flexDirection="column" |
| 344 | transition="all 0.25s" |
| 345 | _hover={{ |
| 346 | borderColor: 'rgba(117,69,187,0.4)', |
| 347 | boxShadow: '0 8px 24px -12px rgba(117,69,187,0.18)', |
| 348 | transform: 'translateY(-2px)', |
| 349 | }} |
| 350 | > |
| 351 | {/* Image area */} |
| 352 | <Box |
| 353 | position="relative" |
| 354 | borderBottom="1px solid #e2e8f0" |
| 355 | pt="20px" |
| 356 | px="20px" |
| 357 | pb="0" |
| 358 | display="flex" |
| 359 | alignItems="flex-start" |
| 360 | justifyContent="center" |
| 361 | overflow="hidden" |
| 362 | background="linear-gradient(129deg, #F3F2F8 2.83%, #F7F5F9 110.96%)" |
| 363 | minH="160px" |
| 364 | > |
| 365 | {/* Image wrapper — white card with shadow */} |
| 366 | <Box |
| 367 | position="relative" |
| 368 | w="100%" |
| 369 | bg="white" |
| 370 | borderRadius="8px 8px 0 0" |
| 371 | border="1px solid #e2e8f0" |
| 372 | borderBottom="none" |
| 373 | overflow="hidden" |
| 374 | boxShadow="0 6px 24px 0 #E5E1EF" |
| 375 | > |
| 376 | {/* Pro badge inside image wrapper */} |
| 377 | {template.isPro && ( |
| 378 | <Box |
| 379 | as="span" |
| 380 | position="absolute" |
| 381 | top="10px" |
| 382 | right="10px" |
| 383 | fontSize="10px" |
| 384 | fontWeight="700" |
| 385 | textTransform="uppercase" |
| 386 | letterSpacing="0.06em" |
| 387 | color="#7545BB" |
| 388 | bg="#f3eefc" |
| 389 | border="1px solid #e6ddf6" |
| 390 | px="8px" |
| 391 | py="2px" |
| 392 | borderRadius="4px" |
| 393 | zIndex={2} |
| 394 | display="inline-flex" |
| 395 | alignItems="center" |
| 396 | > |
| 397 | {__('Pro', 'everest-forms')} |
| 398 | </Box> |
| 399 | )} |
| 400 | <Image |
| 401 | src={template.imageUrl} |
| 402 | alt={template.title} |
| 403 | display="block" |
| 404 | w="100%" |
| 405 | h="auto" |
| 406 | objectFit="cover" |
| 407 | objectPosition="top" |
| 408 | borderRadius="6px" |
| 409 | loading="lazy" |
| 410 | /> |
| 411 | </Box> |
| 412 | |
| 413 | {/* Hover overlay — dark gradient */} |
| 414 | <Box |
| 415 | position="absolute" |
| 416 | inset="0" |
| 417 | bgGradient="linear(to-b, rgba(14,14,14,0.2), rgba(14,14,14,0.4), rgba(14,14,14,0.6))" |
| 418 | opacity={isHovered ? 1 : 0} |
| 419 | transition="opacity 0.3s" |
| 420 | display="flex" |
| 421 | flexDirection="column" |
| 422 | alignItems="center" |
| 423 | justifyContent="center" |
| 424 | gap="8px" |
| 425 | px="20px" |
| 426 | zIndex={2} |
| 427 | > |
| 428 | {/* Favorite button */} |
| 429 | <Box |
| 430 | as="button" |
| 431 | onClick={(e) => { |
| 432 | e.stopPropagation(); |
| 433 | handleFavoriteToggle(template.slug); |
| 434 | }} |
| 435 | aria-label={`Toggle favorite for ${template.title}`} |
| 436 | position="absolute" |
| 437 | top="12px" |
| 438 | right="12px" |
| 439 | w="28px" |
| 440 | h="28px" |
| 441 | display="inline-flex" |
| 442 | alignItems="center" |
| 443 | justifyContent="center" |
| 444 | borderRadius="full" |
| 445 | bg={ |
| 446 | favorites.includes(template.slug) |
| 447 | ? 'white' |
| 448 | : 'rgba(255,255,255,0.15)' |
| 449 | } |
| 450 | backdropFilter="blur(4px)" |
| 451 | color={ |
| 452 | favorites.includes(template.slug) ? 'red.500' : 'white' |
| 453 | } |
| 454 | border="none" |
| 455 | cursor="pointer" |
| 456 | _hover={{ |
| 457 | bg: 'white', |
| 458 | color: 'red.500', |
| 459 | }} |
| 460 | transition="all 0.2s" |
| 461 | > |
| 462 | <Icon |
| 463 | as={ |
| 464 | favorites.includes(template.slug) |
| 465 | ? FaHeart |
| 466 | : FaRegHeart |
| 467 | } |
| 468 | boxSize="3.5" |
| 469 | /> |
| 470 | </Box> |
| 471 | |
| 472 | {/* Use Template button */} |
| 473 | <Button |
| 474 | w="170px" |
| 475 | h="36px" |
| 476 | borderRadius="8px" |
| 477 | bg="#7545BB" |
| 478 | color="white" |
| 479 | fontSize="14px" |
| 480 | fontWeight="500" |
| 481 | boxShadow="0 6px 18px -6px rgba(117,69,187,0.55)" |
| 482 | _hover={{ bg: 'rgba(117,69,187,0.9)' }} |
| 483 | opacity={isHovered ? 1 : 0} |
| 484 | transform={ |
| 485 | isHovered ? 'translateY(0)' : 'translateY(8px)' |
| 486 | } |
| 487 | transition="all 0.3s" |
| 488 | onClick={() => handleTemplateClick(template)} |
| 489 | > |
| 490 | {__('Use this template', 'everest-forms')} |
| 491 | </Button> |
| 492 | |
| 493 | {/* Preview button */} |
| 494 | {template.preview_link && ( |
| 495 | <Button |
| 496 | w="170px" |
| 497 | h="36px" |
| 498 | borderRadius="8px" |
| 499 | bg="transparent" |
| 500 | border="1px solid rgba(255,255,255,0.4)" |
| 501 | color="white" |
| 502 | fontSize="14px" |
| 503 | fontWeight="500" |
| 504 | _hover={{ bg: 'rgba(255,255,255,0.1)' }} |
| 505 | opacity={isHovered ? 1 : 0} |
| 506 | transform={ |
| 507 | isHovered ? 'translateY(0)' : 'translateY(8px)' |
| 508 | } |
| 509 | transition="all 0.3s 0.06s" |
| 510 | onClick={() => |
| 511 | window.open(template.preview_link, '_blank') |
| 512 | } |
| 513 | > |
| 514 | {__('Preview', 'everest-forms')} |
| 515 | </Button> |
| 516 | )} |
| 517 | </Box> |
| 518 | </Box> |
| 519 | |
| 520 | {/* Card info */} |
| 521 | <Box p="20px" flex="1" display="flex" flexDirection="column"> |
| 522 | <Text |
| 523 | className="template-title" |
| 524 | fontSize="14px" |
| 525 | fontWeight="600" |
| 526 | color="#0e0e0e" |
| 527 | mb="4px" |
| 528 | margin="0 0 4px 0" |
| 529 | transition="color 0.2s" |
| 530 | sx={{ '.template-card:hover &': { color: '#7545BB' } }} |
| 531 | > |
| 532 | {template.title} |
| 533 | </Text> |
| 534 | <Text |
| 535 | fontSize="12px" |
| 536 | color="#6b6b6b" |
| 537 | lineHeight="1.6" |
| 538 | margin="0" |
| 539 | flex="1" |
| 540 | > |
| 541 | {template.description} |
| 542 | </Text> |
| 543 | </Box> |
| 544 | </Box> |
| 545 | ); |
| 546 | })} |
| 547 | </Box> |
| 548 | ) : ( |
| 549 | <Box |
| 550 | display="flex" |
| 551 | flexDirection="column" |
| 552 | justifyContent="center" |
| 553 | alignItems="center" |
| 554 | minH="400px" |
| 555 | width="100%" |
| 556 | > |
| 557 | <Image |
| 558 | src={notFoundImage} |
| 559 | alt={__('Not Found', 'everest-forms')} |
| 560 | boxSize="260px" |
| 561 | objectFit="cover" |
| 562 | /> |
| 563 | <Text mt={4} fontSize="lg" fontWeight="bold" textAlign="center"> |
| 564 | {__('No Templates Found', 'everest-forms')} |
| 565 | </Text> |
| 566 | <Text margin={0} fontSize="sm" textAlign="center" color="gray.600"> |
| 567 | {__( |
| 568 | "Sorry, we didn't find any templates that match your criteria", |
| 569 | 'everest-forms', |
| 570 | )} |
| 571 | </Text> |
| 572 | </Box> |
| 573 | )} |
| 574 | |
| 575 | {/* ── Premium / locked template modal ────────────────────────────── */} |
| 576 | <Modal |
| 577 | isCentered |
| 578 | isOpen={isPluginModalOpen} |
| 579 | onClose={closePluginModal} |
| 580 | size="md" |
| 581 | > |
| 582 | <ModalOverlay bg="rgba(0,0,0,0.35)" backdropFilter="blur(2px)" /> |
| 583 | <ModalContent |
| 584 | borderRadius="16px" |
| 585 | p="0" |
| 586 | overflow="hidden" |
| 587 | boxShadow="0 8px 32px rgba(0,0,0,0.1)" |
| 588 | > |
| 589 | <ModalHeader p="0"> |
| 590 | <Flex |
| 591 | align="center" |
| 592 | gap="12px" |
| 593 | px="24px" |
| 594 | pt="22px" |
| 595 | pb="16px" |
| 596 | borderBottom="1px solid #f1f5f9" |
| 597 | > |
| 598 | <Box |
| 599 | w="36px" |
| 600 | h="36px" |
| 601 | borderRadius="8px" |
| 602 | bg="rgba(117,69,187,0.1)" |
| 603 | display="flex" |
| 604 | alignItems="center" |
| 605 | justifyContent="center" |
| 606 | flexShrink={0} |
| 607 | > |
| 608 | <Icon as={LuSparkles} boxSize="16px" color="#7545BB" /> |
| 609 | </Box> |
| 610 | <Box flex="1" minW="0"> |
| 611 | <Text |
| 612 | fontSize="15px" |
| 613 | fontWeight="600" |
| 614 | color="#0e0e0e" |
| 615 | m="0" |
| 616 | noOfLines={1} |
| 617 | > |
| 618 | {lockedTemplateName} |
| 619 | </Text> |
| 620 | <Text fontSize="12px" color="#9ca3af" m="0"> |
| 621 | {__('Premium Template', 'everest-forms')} |
| 622 | </Text> |
| 623 | </Box> |
| 624 | </Flex> |
| 625 | </ModalHeader> |
| 626 | <ModalCloseButton |
| 627 | top="14px" |
| 628 | right="16px" |
| 629 | size="sm" |
| 630 | borderRadius="6px" |
| 631 | _hover={{ bg: '#f1f5f9' }} |
| 632 | /> |
| 633 | |
| 634 | <ModalBody px="24px" py="20px"> |
| 635 | <Text fontSize="13px" color="#6b7280" lineHeight="1.65" m="0"> |
| 636 | {__( |
| 637 | 'This template requires a premium plan. Upgrade to unlock all premium templates and features.', |
| 638 | 'everest-forms', |
| 639 | )} |
| 640 | </Text> |
| 641 | </ModalBody> |
| 642 | |
| 643 | <Box px="24px" pb="22px"> |
| 644 | <Flex gap="10px"> |
| 645 | <Box |
| 646 | as="button" |
| 647 | flex="1" |
| 648 | h="38px" |
| 649 | borderRadius="8px" |
| 650 | border="1px solid #e2e8f0" |
| 651 | bg="white" |
| 652 | color="#374151" |
| 653 | fontSize="13px" |
| 654 | fontWeight="500" |
| 655 | cursor="pointer" |
| 656 | onClick={closePluginModal} |
| 657 | _hover={{ bg: '#f8fafc' }} |
| 658 | transition="background 0.2s" |
| 659 | > |
| 660 | {__('Cancel', 'everest-forms')} |
| 661 | </Box> |
| 662 | <Box |
| 663 | as="a" |
| 664 | href="https://everestforms.net/upgrade/?utm_medium=evf-template-page&utm_source=evf-free&utm_campaign=template-premium-popup" |
| 665 | target="_blank" |
| 666 | rel="noopener noreferrer" |
| 667 | flex="1" |
| 668 | h="38px" |
| 669 | borderRadius="8px" |
| 670 | bg="#7545BB" |
| 671 | color="white" |
| 672 | fontSize="13px" |
| 673 | fontWeight="500" |
| 674 | cursor="pointer" |
| 675 | display="flex" |
| 676 | alignItems="center" |
| 677 | justifyContent="center" |
| 678 | gap="6px" |
| 679 | _hover={{ bg: '#6a3daa', color: 'white' }} |
| 680 | transition="background 0.2s" |
| 681 | textDecoration="none" |
| 682 | > |
| 683 | {__('Upgrade Plan', 'everest-forms')} |
| 684 | </Box> |
| 685 | </Flex> |
| 686 | </Box> |
| 687 | </ModalContent> |
| 688 | </Modal> |
| 689 | |
| 690 | {/* ── Template addon / choose modal ───────────────────────────────── */} |
| 691 | <Modal isCentered isOpen={isOpen} onClose={onClose} size="md"> |
| 692 | <ModalOverlay bg="rgba(0,0,0,0.4)" backdropFilter="blur(2px)" /> |
| 693 | <ModalContent |
| 694 | borderRadius="16px" |
| 695 | p="0" |
| 696 | overflow="hidden" |
| 697 | boxShadow="0 20px 60px rgba(0,0,0,0.12)" |
| 698 | > |
| 699 | {/* Header */} |
| 700 | <ModalHeader |
| 701 | px="24px" |
| 702 | pt="22px" |
| 703 | pb="16px" |
| 704 | borderBottom="1px solid #f1f5f9" |
| 705 | p="0" |
| 706 | > |
| 707 | <Flex |
| 708 | align="center" |
| 709 | gap="12px" |
| 710 | px="24px" |
| 711 | pt="22px" |
| 712 | pb="16px" |
| 713 | borderBottom="1px solid #f1f5f9" |
| 714 | > |
| 715 | <Box |
| 716 | w="36px" |
| 717 | h="36px" |
| 718 | borderRadius="8px" |
| 719 | bg="rgba(117,69,187,0.1)" |
| 720 | display="flex" |
| 721 | alignItems="center" |
| 722 | justifyContent="center" |
| 723 | flexShrink={0} |
| 724 | > |
| 725 | <Icon as={LuSparkles} boxSize="16px" color="#7545BB" /> |
| 726 | </Box> |
| 727 | <Box flex="1" minW="0"> |
| 728 | <Text |
| 729 | fontSize="15px" |
| 730 | fontWeight="600" |
| 731 | color="#0e0e0e" |
| 732 | m="0" |
| 733 | noOfLines={1} |
| 734 | > |
| 735 | {previewTemplate?.title} |
| 736 | </Text> |
| 737 | <Text fontSize="12px" color="#9ca3af" m="0"> |
| 738 | {modalState === 'addons' |
| 739 | ? __('Required addons', 'everest-forms') |
| 740 | : __('Ready to use', 'everest-forms')} |
| 741 | </Text> |
| 742 | </Box> |
| 743 | </Flex> |
| 744 | </ModalHeader> |
| 745 | <ModalCloseButton |
| 746 | top="14px" |
| 747 | right="16px" |
| 748 | size="sm" |
| 749 | borderRadius="6px" |
| 750 | _hover={{ bg: '#f1f5f9' }} |
| 751 | /> |
| 752 | |
| 753 | <ModalBody px="24px" py="20px"> |
| 754 | {/* ── Addons state ── */} |
| 755 | {modalState === 'addons' && ( |
| 756 | <VStack align="stretch" spacing="0"> |
| 757 | <HStack spacing="8px" mb="16px"> |
| 758 | <Box |
| 759 | w="4px" |
| 760 | h="4px" |
| 761 | borderRadius="full" |
| 762 | bg="#7545BB" |
| 763 | flexShrink={0} |
| 764 | mt="1px" |
| 765 | /> |
| 766 | <Text fontSize="13px" color="#6b7280" m="0" lineHeight="1.55"> |
| 767 | {__( |
| 768 | 'This template requires the following addons to be installed and activated:', |
| 769 | 'everest-forms', |
| 770 | )} |
| 771 | </Text> |
| 772 | </HStack> |
| 773 | <PluginStatus |
| 774 | requiredPlugins={requiredPlugins} |
| 775 | onActivateAndContinue={handleAddonsReady} |
| 776 | /> |
| 777 | </VStack> |
| 778 | )} |
| 779 | |
| 780 | {/* ── Choose state ── */} |
| 781 | {modalState === 'choose' && ( |
| 782 | <VStack align="stretch" spacing="16px"> |
| 783 | {/* Primary: Use this template */} |
| 784 | <Box |
| 785 | as="button" |
| 786 | w="100%" |
| 787 | h="44px" |
| 788 | display="inline-flex" |
| 789 | alignItems="center" |
| 790 | justifyContent="center" |
| 791 | gap="8px" |
| 792 | borderRadius="10px" |
| 793 | bg="#7545BB" |
| 794 | color="white" |
| 795 | fontSize="15px" |
| 796 | fontWeight="600" |
| 797 | border="none" |
| 798 | cursor={isCreating ? 'not-allowed' : 'pointer'} |
| 799 | opacity={isCreating ? 0.7 : 1} |
| 800 | onClick={!isCreating ? handleCreateForm : undefined} |
| 801 | transition="background 0.2s, opacity 0.2s" |
| 802 | _hover={!isCreating ? { bg: '#6a3daa' } : {}} |
| 803 | > |
| 804 | {isCreating ? ( |
| 805 | <Box |
| 806 | w="16px" |
| 807 | h="16px" |
| 808 | border="2px solid rgba(255,255,255,0.4)" |
| 809 | borderTopColor="white" |
| 810 | borderRadius="full" |
| 811 | sx={{ |
| 812 | animation: 'spin 0.7s linear infinite', |
| 813 | '@keyframes spin': { |
| 814 | from: { transform: 'rotate(0deg)' }, |
| 815 | to: { transform: 'rotate(360deg)' }, |
| 816 | }, |
| 817 | }} |
| 818 | /> |
| 819 | ) : ( |
| 820 | <Icon as={FiArrowRight} boxSize="4" /> |
| 821 | )} |
| 822 | <Text margin="0" color="white"> |
| 823 | {isCreating |
| 824 | ? __('Creating…', 'everest-forms') |
| 825 | : __('Use this template', 'everest-forms')} |
| 826 | </Text> |
| 827 | </Box> |
| 828 | |
| 829 | {/* OR divider */} |
| 830 | <Flex align="center" gap="12px"> |
| 831 | <Box flex="1" h="1px" bg="#e2e8f0" /> |
| 832 | <Text |
| 833 | fontSize="12px" |
| 834 | fontWeight="500" |
| 835 | color="#9ca3af" |
| 836 | m="0" |
| 837 | textTransform="uppercase" |
| 838 | letterSpacing="0.05em" |
| 839 | > |
| 840 | {__('or', 'everest-forms')} |
| 841 | </Text> |
| 842 | <Box flex="1" h="1px" bg="#e2e8f0" /> |
| 843 | </Flex> |
| 844 | |
| 845 | {/* Secondary: Edit with AI */} |
| 846 | <Box |
| 847 | as="button" |
| 848 | w="100%" |
| 849 | display="inline-flex" |
| 850 | alignItems="center" |
| 851 | justifyContent="center" |
| 852 | gap="8px" |
| 853 | py="10px" |
| 854 | borderRadius="10px" |
| 855 | bg="transparent" |
| 856 | border="none" |
| 857 | color="#7545BB" |
| 858 | fontSize="14px" |
| 859 | fontWeight="500" |
| 860 | disabled={!AI_ENABLED} |
| 861 | title={ |
| 862 | AI_ENABLED |
| 863 | ? undefined |
| 864 | : __('Not available on local sites', 'everest-forms') |
| 865 | } |
| 866 | cursor={ |
| 867 | !AI_ENABLED || aiCreatingSlug ? 'not-allowed' : 'pointer' |
| 868 | } |
| 869 | opacity={!AI_ENABLED ? 0.6 : aiCreatingSlug ? 0.7 : 1} |
| 870 | onClick={() => { |
| 871 | if (AI_ENABLED && !aiCreatingSlug && previewTemplate) |
| 872 | handleEditWithAI(previewTemplate); |
| 873 | }} |
| 874 | transition="color 0.2s" |
| 875 | _hover={AI_ENABLED ? { color: '#6a3daa' } : {}} |
| 876 | > |
| 877 | <Icon as={LuSparkles} boxSize="4" /> |
| 878 | <Text margin="0"> |
| 879 | {aiCreatingSlug |
| 880 | ? __('Loading…', 'everest-forms') |
| 881 | : __('Edit with AI', 'everest-forms')} |
| 882 | </Text> |
| 883 | </Box> |
| 884 | </VStack> |
| 885 | )} |
| 886 | </ModalBody> |
| 887 | </ModalContent> |
| 888 | </Modal> |
| 889 | </Box> |
| 890 | ); |
| 891 | }; |
| 892 | |
| 893 | export default TemplateList; |
| 894 |