← All changes
|
resources/js/components/modules/PremiumUpgradeDialog.tsx
+159
-34
3.0.4
→
3.0.16
View file →
| @@ -22,21 +22,140 @@ | ||
| 22 | 22 | interface PremiumUpgradeDialogProps { |
| 23 | 23 | open: boolean; |
| 24 | 24 | onClose: () => void; |
| 25 | 25 | moduleName?: string; |
| 26 | + /** Module slug — used to pick module-specific bullet copy. */ | |
| 27 | + moduleSlug?: string; | |
| 26 | 28 | purchaseUrl?: string; |
| 27 | 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"; | |
| 28 | 36 | } |
| 29 | 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 | + | |
| 30 | 99 | export const PremiumUpgradeDialog: React.FC<PremiumUpgradeDialogProps> = ({ |
| 31 | 100 | open, |
| 32 | 101 | onClose, |
| 33 | 102 | moduleName, |
| 103 | + moduleSlug, | |
| 34 | 104 | purchaseUrl, |
| 35 | 105 | moduleDescription, |
| 106 | + requiredPlan, | |
| 36 | 107 | }) => { |
| 37 | 108 | if (!open) return null; |
| 38 | 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 | + | |
| 39 | 158 | return createPortal( |
| 40 | 159 | <div |
| 41 | 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" |
| 42 | 161 | style={{ |
| @@ -61,17 +180,39 @@ | ||
| 61 | 180 | <div className="flex items-start gap-4"> |
| 62 | 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"> |
| 63 | 182 | <Crown className="w-6 h-6 text-white" /> |
| 64 | 183 | </div> |
| 65 | - <div> | |
| 66 | - <CardTitle className="text-xl font-bold text-gray-900 dark:text-white"> | |
| 67 | - {moduleName | |
| 68 | - ? `${moduleName} - Premium Feature` | |
| 69 | - : "Premium Feature"} | |
| 70 | - </CardTitle> | |
| 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> | |
| 71 | 209 | <CardDescription className="text-gray-600 dark:text-gray-400 mt-1"> |
| 72 | 210 | {moduleDescription || |
| 73 | - "This feature requires Yatra Pro to unlock powerful premium capabilities."} | |
| 211 | + __( | |
| 212 | + "This feature requires Yatra Pro to unlock powerful premium capabilities.", | |
| 213 | + "yatra", | |
| 214 | + )} | |
| 74 | 215 | </CardDescription> |
| 75 | 216 | </div> |
| 76 | 217 | </div> |
| 77 | 218 | <Button |
| @@ -103,9 +244,11 @@ | ||
| 103 | 244 | )} |
| 104 | 245 | </p> |
| 105 | 246 | </div> |
| 106 | 247 | |
| 107 | - {/* Feature List */} | |
| 248 | + {/* Feature List — module-specific bullets so the | |
| 249 | + operator sees exactly what they're getting, not | |
| 250 | + a generic placeholder list. */} | |
| 108 | 251 | <div> |
| 109 | 252 | <h4 className="font-semibold text-gray-900 dark:text-white mb-3 flex items-center gap-2"> |
| 110 | 253 | <Sparkles className="w-4 h-4 text-orange-500" /> |
| 111 | 254 | {__("Key Features", "yatra")} |
| @@ -110,32 +253,14 @@ | ||
| 110 | 253 | <Sparkles className="w-4 h-4 text-orange-500" /> |
| 111 | 254 | {__("Key Features", "yatra")} |
| 112 | 255 | </h4> |
| 113 | 256 | <ul className="space-y-2 text-sm text-gray-600 dark:text-gray-400"> |
| 114 | - <li className="flex items-start gap-2"> | |
| 115 | - <CheckCircle2 className="w-4 h-4 text-green-500 flex-shrink-0 mt-0.5" /> | |
| 116 | - <span> | |
| 117 | - {__("Advanced analytics and reporting", "yatra")} | |
| 118 | - </span> | |
| 119 | - </li> | |
| 120 | - <li className="flex items-start gap-2"> | |
| 121 | - <CheckCircle2 className="w-4 h-4 text-green-500 flex-shrink-0 mt-0.5" /> | |
| 122 | - <span> | |
| 123 | - {__("Automated workflows and notifications", "yatra")} | |
| 124 | - </span> | |
| 125 | - </li> | |
| 126 | - <li className="flex items-start gap-2"> | |
| 127 | - <CheckCircle2 className="w-4 h-4 text-green-500 flex-shrink-0 mt-0.5" /> | |
| 128 | - <span> | |
| 129 | - {__("Enhanced customer management tools", "yatra")} | |
| 130 | - </span> | |
| 131 | - </li> | |
| 132 | - <li className="flex items-start gap-2"> | |
| 133 | - <CheckCircle2 className="w-4 h-4 text-green-500 flex-shrink-0 mt-0.5" /> | |
| 134 | - <span> | |
| 135 | - {__("Priority support and updates", "yatra")} | |
| 136 | - </span> | |
| 137 | - </li> | |
| 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 | + ))} | |
| 138 | 263 | </ul> |
| 139 | 264 | </div> |
| 140 | 265 | </div> |
| 141 | 266 | </div> |
| @@ -163,9 +288,9 @@ | ||
| 163 | 288 | <div className="border-t border-gray-200 dark:border-gray-800 p-6 bg-gray-50 dark:bg-gray-900/50"> |
| 164 | 289 | <div className="flex flex-col sm:flex-row items-center justify-between gap-4"> |
| 165 | 290 | <div className="text-center sm:text-left"> |
| 166 | 291 | <p className="text-sm text-gray-600 dark:text-gray-400"> |
| 167 | - Upgrade to Yatra Pro and unlock this feature today | |
| 292 | + {planFooterCopy} | |
| 168 | 293 | </p> |
| 169 | 294 | </div> |
| 170 | 295 | <Button |
| 171 | 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" |
| @@ -176,9 +301,9 @@ | ||
| 176 | 301 | ) |
| 177 | 302 | } |
| 178 | 303 | > |
| 179 | 304 | <Sparkles className="w-4 h-4 mr-2 group-hover:rotate-12 transition-transform" /> |
| 180 | - {__("Upgrade to Pro", "yatra")} | |
| 305 | + {planCtaLabel} | |
| 181 | 306 | <ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" /> |
| 182 | 307 | </Button> |
| 183 | 308 | </div> |
| 184 | 309 | </div> |