PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.16
Yatra – Travel Booking & Tour Operator Software v3.0.16
3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 All 84 releases
← All changes | resources/js/pages/Modules.tsx +51 -24 3.0.5 → 3.0.16 View file →
@@ -4,10 +4,9 @@
4 4 Search,
5 5 Filter,
6 6 ArrowUpDown,
7 7 ExternalLink,
8 - Crown,
9 - Sparkles,
8 + ArrowUpRight,
10 9 } from "lucide-react";
11 10 import { __ } from "../lib/i18n";
12 11 import { PageHeader } from "../components/common/PageHeader";
13 12 import {
@@ -30,8 +29,25 @@
30 29 } from "../hooks/useModules";
31 30 import { usePermissions } from "../hooks/usePermissions";
32 31 import { PremiumUpgradeDialog } from "../components/modules/PremiumUpgradeDialog";
33 32
33 +/**
34 + * Build the "Learn more" link to a module's dedicated feature page on
35 + * wpyatra.com (e.g. https://wpyatra.com/features/dynamic-pricing/), tagged with
36 + * UTM params. The feature-page slug matches the docs slug, so we derive it from
37 + * the module's docs_url last segment (defined once per module on the backend).
38 + * Returns null when there's no docs_url to derive a slug from.
39 + */
40 +const featureUrlFromDocs = (docsUrl?: string): string | null => {
41 + if (!docsUrl) return null;
42 + const slug = docsUrl.replace(/\/+$/, "").split("/").pop();
43 + if (!slug) return null;
44 + const params =
45 + "utm_source=plugin&utm_medium=modules&utm_campaign=learn-more&utm_content=" +
46 + encodeURIComponent(slug);
47 + return `https://wpyatra.com/features/${slug}/?${params}`;
48 +};
49 +
34 50 const Modules: React.FC = () => {
35 51 const { can } = usePermissions();
36 52 const canManageModules = can("yatra_edit_trips");
37 53 const { data: modules = [], isLoading, error } = useModulesQuery();
@@ -69,13 +85,16 @@
69 85 const order: ModulePlan[] = ["free", "personal", "growth", "agency"];
70 86 return order.filter((tier) => set.has(tier));
71 87 }, [modules]);
72 88
89 + // 2026 plan rename: display labels changed (Personal→Starter, Agency→Scale)
90 + // but the plan *slugs* ('personal'/'agency') stay — they mirror the Pro
91 + // internal tier slugs and keep existing-user feature gating intact.
73 92 const planLabels: Record<ModulePlan, string> = {
74 93 free: __("Free", "yatra"),
75 - personal: __("Personal", "yatra"),
94 + personal: __("Starter", "yatra"),
76 95 growth: __("Growth", "yatra"),
77 - agency: __("Agency", "yatra"),
96 + agency: __("Scale", "yatra"),
78 97 };
79 98
80 99 const filteredModules = useMemo(() => {
81 100 let list = modules;
@@ -526,18 +545,18 @@
526 545 {module.name}
527 546 {module.is_core && (
528 547 <Badge variant="outline">{__("Core", "yatra")}</Badge>
529 548 )}
530 - {/* Plan badge — Personal for any Pro module, Agency
549 + {/* Plan badge — Starter for any Pro module, Scale
531 550 for white-label-tier modules. Always visible so
532 - customers know which plan unlocks the module. */}
551 + customers know which plan unlocks the module.
552 + (Slug stays 'agency'; label is the new "Scale".) */}
533 553 {module.plan === "agency" && (
534 554 <Badge
535 555 variant="outline"
536 - className="flex items-center gap-1 px-2 py-0.5 rounded-full bg-gradient-to-r from-purple-600 to-indigo-500 border-purple-700 text-white shadow-sm dark:from-purple-500 dark:to-indigo-400 dark:border-purple-400 dark:text-white"
556 + className="px-2 py-0.5 rounded-full bg-gradient-to-r from-purple-600 to-indigo-500 border-purple-700 text-white shadow-sm dark:from-purple-500 dark:to-indigo-400 dark:border-purple-400 dark:text-white"
537 557 >
538 - <Crown className="w-3 h-3" />
539 - {__("Agency", "yatra")}
558 + {__("Scale", "yatra")}
540 559 </Badge>
541 560 )}
542 561 {/* Growth tier — AI Assistant + any future module
543 562 requiring the Growth-or-Agency band. Without
@@ -546,11 +565,10 @@
546 565 unlocks AI features. */}
547 566 {module.plan === "growth" && (
548 567 <Badge
549 568 variant="outline"
550 - className="flex items-center gap-1 px-2 py-0.5 rounded-full bg-gradient-to-r from-emerald-500 to-teal-500 border-emerald-600 text-white shadow-sm dark:from-emerald-500 dark:to-teal-400 dark:border-emerald-400 dark:text-white"
569 + className="px-2 py-0.5 rounded-full bg-gradient-to-r from-emerald-500 to-teal-500 border-emerald-600 text-white shadow-sm dark:from-emerald-500 dark:to-teal-400 dark:border-emerald-400 dark:text-white"
551 570 >
552 - <Sparkles className="w-3 h-3" />
553 571 {__("Growth", "yatra")}
554 572 </Badge>
555 573 )}
556 574 {module.plan === "personal" && (
@@ -557,21 +575,11 @@
557 575 <Badge
558 576 variant="outline"
559 577 className="flex items-center gap-1 px-2 py-0.5 rounded-full bg-blue-50 border-blue-200 text-blue-700 dark:bg-blue-900/30 dark:border-blue-500/40 dark:text-blue-200"
560 578 >
561 - {__("Personal", "yatra")}
579 + {__("Starter", "yatra")}
562 580 </Badge>
563 581 )}
564 - {/* Only show Premium badge if module is premium AND not available (Pro not active) */}
565 - {module.is_premium && !module.is_available && (
566 - <Badge
567 - variant="outline"
568 - className="flex items-center gap-1 px-2 py-0.5 rounded-full bg-gradient-to-r from-orange-500 to-orange-400 border-orange-600 text-white shadow-sm dark:bg-orange-600/60 dark:border-orange-500 dark:text-orange-50"
569 - >
570 - <Crown className="w-3 h-3" />
571 - {__("Premium", "yatra")}
572 - </Badge>
573 - )}
574 582 </CardTitle>
575 583 <CardDescription className="mt-1 text-sm line-clamp-3">
576 584 {module.description}
577 585 </CardDescription>
@@ -586,9 +594,12 @@
586 594 </div>
587 595 </div>
588 596 </CardHeader>
589 597 <CardContent className="flex items-center justify-between text-xs text-gray-500 dark:text-gray-400">
590 - <div className="flex items-center gap-2">
598 + <div
599 + className="flex items-center gap-2"
600 + onClick={(e) => e.stopPropagation()}
601 + >
591 602 {module.video_url && (
592 603 <a
593 604 href={module.video_url}
594 605 target="_blank"
@@ -610,9 +621,12 @@
610 621 {__("Video", "yatra")}
611 622 </a>
612 623 )}
613 624 </div>
614 - <div className="flex items-center gap-2">
625 + <div
626 + className="flex items-center gap-2"
627 + onClick={(e) => e.stopPropagation()}
628 + >
615 629 {module.slug === "dynamic_form_field" && module.enabled && (
616 630 <a
617 631 href={`${window.location.origin}/wp-admin/admin.php?page=yatra&subpage=settings`}
618 632 className="flex items-center gap-1 text-gray-600 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200 text-xs font-medium"
@@ -630,8 +644,21 @@
630 644 <circle cx="12" cy="12" r="3"></circle>
631 645 <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
632 646 </svg>
633 647 {__("Settings", "yatra")}
648 + </a>
649 + )}
650 + {featureUrlFromDocs(module.docs_url) && (
651 + <a
652 + href={featureUrlFromDocs(module.docs_url) as string}
653 + target="_blank"
654 + rel="noopener noreferrer"
655 + onClick={(e) => e.stopPropagation()}
656 + className="flex items-center gap-1 text-blue-600 hover:text-blue-500 dark:text-blue-400 text-xs font-medium"
657 + title={__("Learn more about this feature", "yatra")}
658 + >
659 + <ArrowUpRight className="w-3 h-3" />
660 + {__("Learn more", "yatra")}
634 661 </a>
635 662 )}
636 663 {module.docs_url && (
637 664 <a