PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.15
Yatra – Travel Booking & Tour Operator Software v3.0.15
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 2.0.10 All 83 releases
yatra / resources / js / components / modules / PremiumUpgradeDialog.tsx

PremiumUpgradeDialog.tsx in Yatra – Travel Booking & Tour Operator Software 3.0.15, at resources/js/components/modules/PremiumUpgradeDialog.tsx

316 lines 12.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import React from "react";
2 import { createPortal } from "react-dom";
3 import {
4 Crown,
5 X,
6 Sparkles,
7 CheckCircle2,
8 ArrowRight,
9 Zap,
10 Play,
11 } from "lucide-react";
12 import { __ } from "../../lib/i18n";
13 import {
14 Card,
15 CardContent,
16 CardHeader,
17 CardTitle,
18 CardDescription,
19 } from "../ui/card";
20 import { Button } from "../ui/button";
21
22 interface PremiumUpgradeDialogProps {
23 open: boolean;
24 onClose: () => void;
25 moduleName?: string;
26 /** Module slug — used to pick module-specific bullet copy. */
27 moduleSlug?: string;
28 purchaseUrl?: string;
29 moduleDescription?: string;
30 /**
31 * Required plan tier for this feature — drives the badge in the
32 * header AND the CTA copy. When unset, the dialog falls back to
33 * the generic "Yatra Pro" messaging.
34 */
35 requiredPlan?: "personal" | "growth" | "agency";
36 }
37
38 /**
39 * Module-specific bullets shown in the "Key Features" list. Without
40 * these we'd be stuck with the generic four-item placeholder
41 * ("Advanced analytics and reporting", etc.) regardless of which
42 * module the operator is looking at — confusing because the dialog
43 * title already says "White Label" but the bullets say nothing about
44 * branding.
45 */
46 const MODULE_FEATURES: Record<string, string[]> = {
47 white_label: [
48 "Rebrand the plugin: name, logo, author, support URL",
49 "Replace Yatra/MantraBrain references on plugin list + PDFs",
50 "Custom admin menu labels, icons, ordering",
51 "Custom brand color applied across the admin UI",
52 ],
53 "white-label": [
54 "Rebrand the plugin: name, logo, author, support URL",
55 "Replace Yatra/MantraBrain references on plugin list + PDFs",
56 "Custom admin menu labels, icons, ordering",
57 "Custom brand color applied across the admin UI",
58 ],
59 ai_assistant: [
60 "Inline AI generation on trip descriptions, FAQs, SEO meta",
61 "Multi-step agents for full trip + itinerary creation",
62 "AI public chat widget on every single-trip page",
63 "Bring your own OpenAI / Anthropic key — no per-call markup",
64 ],
65 "ai-assistant": [
66 "Inline AI generation on trip descriptions, FAQs, SEO meta",
67 "Multi-step agents for full trip + itinerary creation",
68 "AI public chat widget on every single-trip page",
69 "Bring your own OpenAI / Anthropic key — no per-call markup",
70 ],
71 };
72
73 /**
74 * Fallback bullets per plan tier when the module isn't in
75 * MODULE_FEATURES yet. Tier-appropriate so a "Growth" module's
76 * generic bullets don't accidentally mention Agency-only perks.
77 */
78 const TIER_DEFAULT_FEATURES: Record<string, string[]> = {
79 agency: [
80 "Unlocks every premium module — Scale-tier and below",
81 "Full white-label rebranding capability",
82 "Priority support with faster response times",
83 "All future Scale-tier features included",
84 ],
85 growth: [
86 "Advanced operator-productivity features",
87 "AI assistant for content + itinerary generation",
88 "Priority support included",
89 "All future Growth-tier features included",
90 ],
91 personal: [
92 "Premium booking-system extensions",
93 "Email automation + abandoned-recovery workflows",
94 "Dynamic pricing + advanced discount tools",
95 "All future Starter-tier features included",
96 ],
97 };
98
99 export const PremiumUpgradeDialog: React.FC<PremiumUpgradeDialogProps> = ({
100 open,
101 onClose,
102 moduleName,
103 moduleSlug,
104 purchaseUrl,
105 moduleDescription,
106 requiredPlan,
107 }) => {
108 if (!open) return null;
109
110 // Resolve the customer-facing plan name from the tier slug. The
111 // header badge + footer CTA both consume this so the operator
112 // sees one consistent answer to "which plan do I need?".
113 const planLabel =
114 requiredPlan === "agency"
115 ? __("Scale plan", "yatra")
116 : requiredPlan === "growth"
117 ? __("Growth plan", "yatra")
118 : requiredPlan === "personal"
119 ? __("Starter plan", "yatra")
120 : null;
121 const planCtaLabel =
122 requiredPlan === "agency"
123 ? __("Upgrade to Scale", "yatra")
124 : requiredPlan === "growth"
125 ? __("Upgrade to Growth", "yatra")
126 : requiredPlan === "personal"
127 ? __("Upgrade to Starter", "yatra")
128 : __("Upgrade to Pro", "yatra");
129 const planFooterCopy =
130 requiredPlan === "agency"
131 ? __(
132 "This feature is exclusive to the Scale plan. Upgrade to unlock it.",
133 "yatra",
134 )
135 : requiredPlan === "growth"
136 ? __(
137 "This feature unlocks on the Growth plan (or Scale). Upgrade to enable it.",
138 "yatra",
139 )
140 : requiredPlan === "personal"
141 ? __(
142 "This feature is included on every Pro plan. Upgrade to unlock it.",
143 "yatra",
144 )
145 : __("Upgrade to Yatra Pro and unlock this feature today", "yatra");
146
147 // Module-specific bullets first; fall back to tier-appropriate
148 // defaults; finally fall back to the very generic Pro bullets so
149 // the section never renders empty.
150 const features: string[] = (moduleSlug && MODULE_FEATURES[moduleSlug]) ||
151 (requiredPlan && TIER_DEFAULT_FEATURES[requiredPlan]) || [
152 __("Advanced analytics and reporting", "yatra"),
153 __("Automated workflows and notifications", "yatra"),
154 __("Enhanced customer management tools", "yatra"),
155 __("Priority support and updates", "yatra"),
156 ];
157
158 return createPortal(
159 <div
160 className="fixed top-0 left-0 right-0 bottom-0 m-0 bg-black/60 backdrop-blur-sm flex items-center justify-center px-4 pb-4"
161 style={{
162 zIndex: 999999,
163 position: "fixed",
164 top: 0,
165 left: 0,
166 right: 0,
167 bottom: 0,
168 }}
169 >
170 <div className="relative w-full max-w-5xl">
171 {/* Subtle premium glow */}
172 <div className="absolute -inset-0.5 bg-gradient-to-r from-orange-500/20 via-orange-400/20 to-orange-500/20 rounded-2xl blur-2xl"></div>
173
174 <Card className="relative w-full shadow-2xl border border-orange-300/80 dark:border-orange-700/50 bg-white dark:bg-slate-900 overflow-hidden max-h-[90vh] flex flex-col">
175 {/* Premium accent bar */}
176 <div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-orange-500 via-orange-400 to-orange-500"></div>
177
178 <CardHeader className="relative pb-4 pt-6 border-b border-gray-200 dark:border-gray-800 flex-shrink-0 bg-gradient-to-b from-orange-50/30 to-transparent dark:from-orange-950/20">
179 <div className="flex items-start justify-between">
180 <div className="flex items-start gap-4">
181 <div className="w-12 h-12 rounded-full bg-gradient-to-br from-orange-500 to-orange-600 flex items-center justify-center flex-shrink-0">
182 <Crown className="w-6 h-6 text-white" />
183 </div>
184 <div className="flex-1">
185 <div className="flex flex-wrap items-center gap-2">
186 <CardTitle className="text-xl font-bold text-gray-900 dark:text-white">
187 {moduleName || __("Premium Feature", "yatra")}
188 </CardTitle>
189 {/* Plan-tier badge so the operator immediately
190 knows which license unlocks this. Without it the
191 generic "Premium Feature" copy was ambiguous —
192 Growth and Scale are both premium but cost
193 different amounts. */}
194 {planLabel && (
195 <span
196 className={
197 "inline-flex items-center gap-1 rounded-full px-2.5 py-1 text-xs font-medium text-white shadow-sm " +
198 (requiredPlan === "agency"
199 ? "bg-gradient-to-r from-purple-600 to-indigo-500"
200 : requiredPlan === "growth"
201 ? "bg-gradient-to-r from-emerald-500 to-teal-500"
202 : "bg-gradient-to-r from-blue-500 to-blue-600")
203 }
204 >
205 {planLabel}
206 </span>
207 )}
208 </div>
209 <CardDescription className="text-gray-600 dark:text-gray-400 mt-1">
210 {moduleDescription ||
211 __(
212 "This feature requires Yatra Pro to unlock powerful premium capabilities.",
213 "yatra",
214 )}
215 </CardDescription>
216 </div>
217 </div>
218 <Button
219 variant="ghost"
220 size="sm"
221 onClick={onClose}
222 className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
223 >
224 <X className="w-4 h-4" />
225 </Button>
226 </div>
227 </CardHeader>
228
229 <CardContent className="flex-1 overflow-hidden pt-6 px-6 pb-6">
230 <div className="grid grid-cols-1 lg:grid-cols-2 gap-6 h-full">
231 {/* Left Side - Module Description */}
232 <div className="flex flex-col overflow-y-auto pr-3">
233 <div className="space-y-4">
234 <div>
235 <h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-3 flex items-center gap-2">
236 <Zap className="w-4 h-4 text-orange-500" />
237 {__("What This Module Does", "yatra")}
238 </h3>
239 <p className="text-gray-600 dark:text-gray-400 leading-relaxed">
240 {moduleDescription ||
241 __(
242 "This premium module adds advanced functionality to your Yatra booking system, helping you grow your business and streamline operations.",
243 "yatra",
244 )}
245 </p>
246 </div>
247
248 {/* Feature List — module-specific bullets so the
249 operator sees exactly what they're getting, not
250 a generic placeholder list. */}
251 <div>
252 <h4 className="font-semibold text-gray-900 dark:text-white mb-3 flex items-center gap-2">
253 <Sparkles className="w-4 h-4 text-orange-500" />
254 {__("Key Features", "yatra")}
255 </h4>
256 <ul className="space-y-2 text-sm text-gray-600 dark:text-gray-400">
257 {features.map((line, i) => (
258 <li key={i} className="flex items-start gap-2">
259 <CheckCircle2 className="w-4 h-4 text-green-500 flex-shrink-0 mt-0.5" />
260 <span>{line}</span>
261 </li>
262 ))}
263 </ul>
264 </div>
265 </div>
266 </div>
267
268 {/* Right Side - Video Preview */}
269 <div className="flex flex-col">
270 <div>
271 <h4 className="font-semibold text-gray-900 dark:text-white mb-3 flex items-center gap-2">
272 <Play className="w-4 h-4 text-orange-500" />
273 {__("See It In Action", "yatra")}
274 </h4>
275 <div className="aspect-video bg-gray-100 dark:bg-gray-800 rounded-lg flex items-center justify-center border-2 border-dashed border-gray-300 dark:border-gray-600">
276 <div className="text-center">
277 <Play className="w-12 h-12 text-gray-400 mx-auto mb-2" />
278 <p className="text-sm text-gray-500 dark:text-gray-400">
279 {__("Click to watch demo video", "yatra")}
280 </p>
281 </div>
282 </div>
283 </div>
284 </div>
285 </div>
286 </CardContent>
287
288 <div className="border-t border-gray-200 dark:border-gray-800 p-6 bg-gray-50 dark:bg-gray-900/50">
289 <div className="flex flex-col sm:flex-row items-center justify-between gap-4">
290 <div className="text-center sm:text-left">
291 <p className="text-sm text-gray-600 dark:text-gray-400">
292 {planFooterCopy}
293 </p>
294 </div>
295 <Button
296 className="bg-gradient-to-r from-orange-600 to-orange-500 hover:from-orange-700 hover:to-orange-600 text-white px-6 py-2 font-medium shadow-lg hover:shadow-xl transition-all duration-200 group"
297 onClick={() =>
298 window.open(
299 purchaseUrl || "https://wpyatra.com/pricing",
300 "_blank",
301 )
302 }
303 >
304 <Sparkles className="w-4 h-4 mr-2 group-hover:rotate-12 transition-transform" />
305 {planCtaLabel}
306 <ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />
307 </Button>
308 </div>
309 </div>
310 </Card>
311 </div>
312 </div>,
313 document.body,
314 );
315 };
316