PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.5
Yatra – Travel Booking & Tour Operator Software v3.0.5
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
yatra / resources / js / components / modules / PremiumUpgradeDialog.tsx

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

321 lines 13.0 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 — Agency-tier and below",
81 "Full white-label rebranding capability",
82 "Priority support with faster response times",
83 "All future Agency-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 Personal-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 ? __("Agency plan", "yatra")
116 : requiredPlan === "growth"
117 ? __("Growth plan", "yatra")
118 : requiredPlan === "personal"
119 ? __("Personal plan", "yatra")
120 : null;
121 const planCtaLabel =
122 requiredPlan === "agency"
123 ? __("Upgrade to Agency", "yatra")
124 : requiredPlan === "growth"
125 ? __("Upgrade to Growth", "yatra")
126 : requiredPlan === "personal"
127 ? __("Upgrade to Personal", "yatra")
128 : __("Upgrade to Pro", "yatra");
129 const planFooterCopy =
130 requiredPlan === "agency"
131 ? __(
132 "This feature is exclusive to the Agency plan. Upgrade to unlock it.",
133 "yatra",
134 )
135 : requiredPlan === "growth"
136 ? __(
137 "This feature unlocks on the Growth plan (or Agency). 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 Agency 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 {requiredPlan === "growth" ? (
206 <Sparkles className="h-3 w-3" />
207 ) : (
208 <Crown className="h-3 w-3" />
209 )}
210 {planLabel}
211 </span>
212 )}
213 </div>
214 <CardDescription className="text-gray-600 dark:text-gray-400 mt-1">
215 {moduleDescription ||
216 __(
217 "This feature requires Yatra Pro to unlock powerful premium capabilities.",
218 "yatra",
219 )}
220 </CardDescription>
221 </div>
222 </div>
223 <Button
224 variant="ghost"
225 size="sm"
226 onClick={onClose}
227 className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
228 >
229 <X className="w-4 h-4" />
230 </Button>
231 </div>
232 </CardHeader>
233
234 <CardContent className="flex-1 overflow-hidden pt-6 px-6 pb-6">
235 <div className="grid grid-cols-1 lg:grid-cols-2 gap-6 h-full">
236 {/* Left Side - Module Description */}
237 <div className="flex flex-col overflow-y-auto pr-3">
238 <div className="space-y-4">
239 <div>
240 <h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-3 flex items-center gap-2">
241 <Zap className="w-4 h-4 text-orange-500" />
242 {__("What This Module Does", "yatra")}
243 </h3>
244 <p className="text-gray-600 dark:text-gray-400 leading-relaxed">
245 {moduleDescription ||
246 __(
247 "This premium module adds advanced functionality to your Yatra booking system, helping you grow your business and streamline operations.",
248 "yatra",
249 )}
250 </p>
251 </div>
252
253 {/* Feature List — module-specific bullets so the
254 operator sees exactly what they're getting, not
255 a generic placeholder list. */}
256 <div>
257 <h4 className="font-semibold text-gray-900 dark:text-white mb-3 flex items-center gap-2">
258 <Sparkles className="w-4 h-4 text-orange-500" />
259 {__("Key Features", "yatra")}
260 </h4>
261 <ul className="space-y-2 text-sm text-gray-600 dark:text-gray-400">
262 {features.map((line, i) => (
263 <li key={i} className="flex items-start gap-2">
264 <CheckCircle2 className="w-4 h-4 text-green-500 flex-shrink-0 mt-0.5" />
265 <span>{line}</span>
266 </li>
267 ))}
268 </ul>
269 </div>
270 </div>
271 </div>
272
273 {/* Right Side - Video Preview */}
274 <div className="flex flex-col">
275 <div>
276 <h4 className="font-semibold text-gray-900 dark:text-white mb-3 flex items-center gap-2">
277 <Play className="w-4 h-4 text-orange-500" />
278 {__("See It In Action", "yatra")}
279 </h4>
280 <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">
281 <div className="text-center">
282 <Play className="w-12 h-12 text-gray-400 mx-auto mb-2" />
283 <p className="text-sm text-gray-500 dark:text-gray-400">
284 {__("Click to watch demo video", "yatra")}
285 </p>
286 </div>
287 </div>
288 </div>
289 </div>
290 </div>
291 </CardContent>
292
293 <div className="border-t border-gray-200 dark:border-gray-800 p-6 bg-gray-50 dark:bg-gray-900/50">
294 <div className="flex flex-col sm:flex-row items-center justify-between gap-4">
295 <div className="text-center sm:text-left">
296 <p className="text-sm text-gray-600 dark:text-gray-400">
297 {planFooterCopy}
298 </p>
299 </div>
300 <Button
301 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"
302 onClick={() =>
303 window.open(
304 purchaseUrl || "https://wpyatra.com/pricing",
305 "_blank",
306 )
307 }
308 >
309 <Sparkles className="w-4 h-4 mr-2 group-hover:rotate-12 transition-transform" />
310 {planCtaLabel}
311 <ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />
312 </Button>
313 </div>
314 </div>
315 </Card>
316 </div>
317 </div>,
318 document.body,
319 );
320 };
321