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 / dashboard / screens / Modules / components / AddonCard.js
everest-forms / src / dashboard / screens / Modules / components Last commit date
AddonCard.js 3 weeks ago CardsGrid.js 3 months ago Categories.js 4 months ago Filters.js 3 months ago modules-api.js 1 year ago
AddonCard.js
569 lines
1 /**
2 * External Dependencies
3 */
4 import {
5 Badge,
6 Box,
7 Button,
8 Flex,
9 Heading,
10 HStack,
11 Icon,
12 IconButton,
13 Link,
14 Modal,
15 ModalBody,
16 ModalCloseButton,
17 ModalContent,
18 ModalHeader,
19 ModalOverlay,
20 Spinner,
21 Switch,
22 Text,
23 Tooltip,
24 useDisclosure,
25 VStack,
26 } from '@chakra-ui/react';
27 import { __ } from '@wordpress/i18n';
28 import { useEffect, useMemo, useState } from 'react';
29 import { FaCog, FaPlay } from 'react-icons/fa';
30 import ReactPlayer from 'react-player';
31 import { activateModule, deactivateModule } from './modules-api';
32
33 const AddonCard = ({ addon, showToast, onModuleToggle }) => {
34 const [isActive, setIsActive] = useState(addon.status === 'active');
35 const [isLoading, setIsLoading] = useState(false);
36 const [videoLoading, setVideoLoading] = useState(false);
37 const {
38 isOpen: isVideoOpen,
39 onOpen: onVideoOpen,
40 onClose: onVideoClose,
41 } = useDisclosure();
42
43 const { isPro, licensePlan } =
44 typeof _EVF_DASHBOARD_ !== 'undefined' && _EVF_DASHBOARD_
45 ? _EVF_DASHBOARD_
46 : {};
47
48 const getImageUrl = (imagePath) => {
49 const { assetsURL } =
50 typeof _EVF_DASHBOARD_ !== 'undefined' && _EVF_DASHBOARD_;
51 if (imagePath && assetsURL) {
52 return assetsURL + imagePath;
53 }
54 return imagePath;
55 };
56
57 const moduleEnabled = useMemo(() => {
58 if (!addon.plan || addon.plan.length === 0) {
59 return true;
60 }
61
62 if (Array.isArray(addon.plan) && addon.plan.includes('free')) {
63 return true;
64 }
65
66 if (isPro) {
67 return true;
68 }
69
70 return false;
71 }, [addon.plan, isPro]);
72
73 const handleUpgradePlan = () => {
74 const { upgradeURL } =
75 typeof _EVF_DASHBOARD_ !== 'undefined' && _EVF_DASHBOARD_;
76 if (upgradeURL) {
77 const plan_upgrade_url =
78 upgradeURL +
79 '?utm_source=dashboard-modules&utm_medium=upgrade-button&utm_campaign=' +
80 addon.slug;
81 window.open(plan_upgrade_url, '_blank');
82 }
83 };
84
85 const handleVideoPlay = () => {
86 setVideoLoading(true);
87 onVideoOpen();
88 };
89
90 // Sync local isActive state when addon.status changes (e.g. after category switch)
91 useEffect(() => {
92 setIsActive(addon.status === 'active');
93 }, [addon.status]);
94
95 const MENU_MAP = {
96 'everest-forms-coupons' : {
97 title: __('Coupons', 'everest-forms'),
98 page: "evf-coupons",
99 },
100 'everest-forms-email-templates': {
101 title: __("Email Templates", "everest-forms"),
102 page: "evf-email-templates",
103 },
104 'everest-forms-frontend-listing':{
105 title: __("Frontend List", "everest-forms"),
106 page: "evf-frontend-list",
107 }
108 };
109
110 const appendEverestFormsSubmenu = function (title, pageSlug) {
111 const submenu = document.querySelector(
112 "#toplevel_page_everest-forms .wp-submenu"
113 );
114
115 if (!submenu) return;
116
117 var exists = submenu.querySelector(
118 'a[href="admin.php?page=' + pageSlug + '"]'
119 );
120 if (exists) return;
121
122 var li = document.createElement("li");
123 var a = document.createElement("a");
124
125 a.href = "admin.php?page=" + pageSlug;
126 a.textContent = title;
127
128 li.appendChild(a);
129
130 var settings = submenu.querySelector(
131 'a[href="admin.php?page=evf-settings"]'
132 );
133
134 if (settings && settings.parentElement) {
135 submenu.insertBefore(li, settings.parentElement);
136 } else {
137 submenu.appendChild(li);
138 }
139 };
140
141 const removeEverestFormsSubmenu = function (pageSlug) {
142 const submenu = document.querySelector(
143 "#toplevel_page_everest-forms .wp-submenu"
144 );
145
146 if (!submenu) return;
147
148 var link = submenu.querySelector(
149 'a[href="admin.php?page=' + pageSlug + '"]'
150 );
151
152 if (link && link.parentElement) {
153 link.parentElement.remove();
154 }
155 };
156
157 const syncSidebarMenu = function (slug, enabled) {
158 var config = MENU_MAP[slug];
159 if (!config) return;
160
161 if (enabled) {
162 appendEverestFormsSubmenu(config.title, config.page);
163 } else {
164 removeEverestFormsSubmenu(config.page);
165 }
166 };
167
168 const handleToggle = async () => {
169 setIsLoading(true);
170 try {
171 let response;
172
173 if (isActive) {
174 response = await deactivateModule(addon.slug, addon.type);
175
176 if (response.success) {
177 setIsActive(false);
178 onModuleToggle?.(addon.slug, 'inactive');
179
180 syncSidebarMenu(response.slug || addon.slug, false);
181
182 showToast(
183 response.message || 'Module deactivated successfully',
184 'success',
185 );
186 } else {
187 showToast(
188 response.message || 'Failed to deactivate module',
189 'error',
190 );
191 }
192 } else {
193 response = await activateModule(addon.slug, addon.name, addon.type);
194 if (response.success) {
195 setIsActive(true);
196 onModuleToggle?.(addon.slug, 'active');
197
198 syncSidebarMenu(response.slug || addon.slug, true);
199
200 showToast(
201 response.message || 'Module activated successfully',
202 'success',
203 );
204 } else {
205 showToast(response.message || 'Failed to activate module', 'error');
206 }
207 }
208 } catch (error) {
209 showToast(error.message || 'An error occurred', 'error');
210 }
211 setIsLoading(false);
212 };
213
214 const getPlanBadge = (plan) => {
215 if (!plan || plan.length === 0) {
216 return 'Free';
217 }
218
219 if (Array.isArray(plan) && plan.includes('free')) {
220 return 'Free';
221 }
222 return 'PRO';
223 };
224
225 const getPlanBadgeStyles = (plan) => {
226 const badge = getPlanBadge(plan);
227
228 if (badge === 'Free') {
229 return {
230 bg: 'transparent',
231 border: '1px solid #D1D5DB',
232 color: '#6B7280',
233 fontSize: '11px',
234 fontWeight: '500',
235 };
236 }
237
238 return {
239 bg: '#FFFAF5',
240 border: '1px solid #FF8C39',
241 color: '#FF8C39',
242 fontSize: '11px',
243 fontWeight: '600',
244 };
245 };
246
247 const badgeStyles = getPlanBadgeStyles(addon.plan);
248
249 return (
250 <Box
251 bg="white"
252 borderRadius="lg"
253 border="1px solid"
254 borderColor="gray.200"
255 p={{ base: '4', sm: '5', md: '6' }}
256 _hover={{ boxShadow: 'md' }}
257 transition="all 0.2s"
258 position="relative"
259 height="100%"
260 display="flex"
261 flexDirection="column"
262 >
263 {/* Loading Overlay */}
264 {isLoading && (
265 <Flex
266 position="absolute"
267 top="0"
268 left="0"
269 right="0"
270 bottom="0"
271 bg="rgba(255, 255, 255, 0.8)"
272 borderRadius="xl"
273 alignItems="center"
274 justifyContent="center"
275 zIndex="10"
276 >
277 <Spinner size="lg" color="#7545bb" thickness="3px" />
278 </Flex>
279 )}
280
281 <HStack
282 align="start"
283 spacing={{ base: '3', sm: '4' }}
284 flex="1"
285 mb={{ base: '4', sm: '5', md: '6' }}
286 >
287 <Box
288 w={{ base: '9', sm: '10' }}
289 h={{ base: '9', sm: '10' }}
290 bg="gray.50"
291 borderRadius="full"
292 display="flex"
293 alignItems="center"
294 justifyContent="center"
295 flexShrink={0}
296 overflow="hidden"
297 >
298 {addon.image ? (
299 <img
300 src={getImageUrl(addon.image)}
301 alt={addon.title}
302 style={{
303 width: '100%',
304 height: '100%',
305 objectFit: 'contain',
306 borderRadius: '50%',
307 }}
308 onError={(e) => {
309 e.target.style.display = 'none';
310 if (e.target.nextSibling) {
311 e.target.nextSibling.style.display = 'flex';
312 }
313 }}
314 />
315 ) : null}
316 <Box
317 display={addon.image ? 'none' : 'flex'}
318 alignItems="center"
319 justifyContent="center"
320 fontSize={{ base: 'xl', sm: '2xl' }}
321 width="100%"
322 height="100%"
323 >
324 📋
325 </Box>
326 </Box>
327
328 {/* Right Side - Title and Badge */}
329 <VStack
330 align="start"
331 spacing={{ base: '2', sm: '3' }}
332 flex="1"
333 minW="0"
334 >
335 <HStack justify="space-between" w="full" align="start" spacing="2">
336 <Heading
337 size="sm"
338 color="gray.800"
339 fontWeight="600"
340 fontSize={{ base: '14px', sm: '15px', md: '16px' }}
341 noOfLines={2}
342 flex="1"
343 minW="0"
344 >
345 {addon.title}
346 </Heading>
347 <Badge
348 fontSize={badgeStyles.fontSize}
349 px="2"
350 py="1"
351 borderRadius="base"
352 bg={badgeStyles.bg}
353 border={badgeStyles.border}
354 color={badgeStyles.color}
355 fontWeight={badgeStyles.fontWeight}
356 flexShrink={0}
357 textTransform="uppercase"
358 >
359 {getPlanBadge(addon.plan)}
360 </Badge>
361 </HStack>
362
363 <Tooltip
364 label={
365 addon.excerpt || __('No description available.', 'everest-forms')
366 }
367 placement="bottom"
368 hasArrow
369 bg="white"
370 color="gray.800"
371 fontSize="sm"
372 fontWeight="400"
373 borderRadius="md"
374 px="3"
375 py="2"
376 >
377 <Text
378 fontSize={{ base: '12px', sm: '13px' }}
379 color="gray.600"
380 lineHeight="1.5"
381 noOfLines={2}
382 cursor="help"
383 >
384 {addon.excerpt ||
385 __('No description available.', 'everest-forms')}
386 </Text>
387 </Tooltip>
388 </VStack>
389 </HStack>
390
391 <HStack
392 justify="space-between"
393 align="center"
394 mt="auto"
395 // pt={{ base: '2', sm: '3' }}
396 pt="16px"
397 borderTop="1px solid"
398 borderColor="gray.100"
399 flexWrap={{ base: 'wrap', sm: 'nowrap' }}
400 gap={{ base: '2', sm: '0' }}
401 >
402 <HStack
403 spacing="2"
404 fontSize={{ base: '12px', sm: '13px' }}
405 flexWrap="wrap"
406 flex={{ base: '1', sm: '0 1 auto' }}
407 >
408 {addon.link && (
409 <Link
410 href={addon.link}
411 color="gray.600"
412 textDecoration="none"
413 isExternal
414 _hover={{ color: '#7545bb', textDecoration: 'underline' }}
415 fontWeight="500"
416 >
417 {__('Docs', 'everest-forms')}
418 </Link>
419 )}
420 {addon.demo_video_url && (
421 <>
422 <Text color="gray.300" display={{ base: 'none', sm: 'block' }}>
423 |
424 </Text>
425 <IconButton
426 size="xs"
427 icon={<Icon as={FaPlay} />}
428 aria-label={__('Video Tutorial', 'everest-forms')}
429 variant="ghost"
430 color="gray.600"
431 _hover={{ color: '#7545bb', bg: 'gray.50' }}
432 onClick={handleVideoPlay}
433 />
434 </>
435 )}
436 {addon.setting_url && isActive && (
437 <>
438 <Text color="gray.300" display={{ base: 'none', sm: 'block' }}>
439 |
440 </Text>
441 <IconButton
442 size="xs"
443 icon={<FaCog />}
444 aria-label={__('Settings', 'everest-forms')}
445 variant="ghost"
446 color="gray.600"
447 _hover={{ color: '#7545bb', bg: 'gray.50' }}
448 onClick={() => window.open(addon.setting_url, '_self')}
449 />
450 </>
451 )}
452 </HStack>
453 <HStack spacing="2" flexShrink={0}>
454 {moduleEnabled ? (
455 <Switch
456 isChecked={isActive}
457 onChange={handleToggle}
458 isDisabled={isLoading}
459 size="md"
460 sx={{
461 '& .chakra-switch__track': {
462 bg: isActive ? '#4cc741' : 'gray.300',
463 width:"28px",
464 height:"16px",
465 p:"2px",
466 boxSizing:"border-box",
467 },
468 '& .chakra-switch__track[data-checked]': {
469 bg: '#4cc741',
470 },
471 '& .chakra-switch__thumb': {
472 width:"12px",
473 height:"12px",
474 },
475 '& .chakra-switch__thumb[data-checked]': {
476 transform:"translateX(12px)",
477 },
478 }}
479 />
480 ) : (
481 <Button
482 size="sm"
483 fontSize={{ base: '12px', sm: '13px' }}
484 fontWeight="600"
485 bg="#7545bb"
486 color="white"
487 borderColor="#7545bb"
488 px={{ base: '3', sm: '4' }}
489 h={{ base: '28px', sm: '32px' }}
490 _hover={{
491 bg: '#7545bb',
492 borderColor: '#7545bb',
493 }}
494 _active={{
495 bg: '#7545bb',
496 }}
497 onClick={handleUpgradePlan}
498 >
499 {__('Upgrade Plan', 'everest-forms')}
500 </Button>
501 )}
502 </HStack>
503 </HStack>
504
505 {/* YouTube Video Modal */}
506 {isVideoOpen && addon.demo_video_url && (
507 <Modal
508 isOpen={isVideoOpen}
509 onClose={onVideoClose}
510 size={{ base: 'full', sm: 'xl', md: '2xl', lg: '3xl' }}
511 isCentered
512 >
513 <ModalOverlay bg="blackAlpha.700" />
514 <ModalContent
515 mx={{ base: '0', sm: '4' }}
516 my={{ base: '0', sm: 'auto' }}
517 >
518 <ModalHeader
519 textAlign="center"
520 fontSize={{ base: 'md', sm: 'lg' }}
521 fontWeight="600"
522 px={{ base: '4', sm: '6' }}
523 >
524 {addon.title}
525 </ModalHeader>
526 <ModalCloseButton />
527 <ModalBody pb={{ base: '4', sm: '6' }} px={{ base: '4', sm: '6' }}>
528 <Box
529 position="relative"
530 paddingTop="56.25%"
531 bg="gray.100"
532 borderRadius="md"
533 overflow="hidden"
534 >
535 <ReactPlayer
536 url={`https://www.youtube.com/watch?v=${addon.demo_video_url}`}
537 playing={true}
538 width="100%"
539 height="100%"
540 controls
541 style={{
542 position: 'absolute',
543 top: 0,
544 left: 0,
545 }}
546 onReady={() => setVideoLoading(false)}
547 onStart={() => setVideoLoading(false)}
548 />
549 {videoLoading && (
550 <Box
551 position="absolute"
552 top="50%"
553 left="50%"
554 transform="translate(-50%, -50%)"
555 >
556 <Spinner size="lg" color="#7545bb" />
557 </Box>
558 )}
559 </Box>
560 </ModalBody>
561 </ModalContent>
562 </Modal>
563 )}
564 </Box>
565 );
566 };
567
568 export default AddonCard;
569