PluginProbe
The Innovative Form Builder – IvyForms / 1.1.1
The Innovative Form Builder – IvyForms v1.1.1
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
ivyforms / frontend / src / composables / useProFeatureUpgrade.ts

useProFeatureUpgrade.ts in The Innovative Form Builder – IvyForms 1.1.1, at frontend/src/composables/useProFeatureUpgrade.ts

329 lines 10.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Pro Feature Upgrade Dialog Composable
3 *
4 * Shows appropriate upgrade dialogs based on Pro plugin status:
5 * 1. Pro NOT installed/active -> Notification Pop-up (simple)
6 * 2. Pro installed but license not activated -> Activation Pop-up (detailed)
7 */
8
9 import { useProUpgradeDialogStore } from '@/stores/proUpgradeDialogStore'
10 import { useLabels } from '@/composables/useLabels'
11 import { useSettingsStore } from '@/stores/useSettingsStore'
12 import { useProFeatures } from '@/composables/useProFeatures'
13 import { IVYFORMS_WEBSITE_PRICING } from '@/constants/links'
14
15 type ProFeatureType = string
16
17 /**
18 * Open pricing page in a new tab with proper security attributes
19 * Centralized function to handle all pricing page navigation
20 */
21 export function openPricingPage() {
22 const link = document.createElement('a')
23 link.href = IVYFORMS_WEBSITE_PRICING
24 link.target = '_blank'
25 link.rel = 'noopener noreferrer nofollow'
26 link.click()
27 }
28
29 export function useProFeatureUpgrade() {
30 const proUpgradeDialog = useProUpgradeDialogStore()
31 const { getLabel } = useLabels()
32 const settingsStore = useSettingsStore()
33
34 /**
35 * Get all available Pro field features from the plan
36 * These are loaded from the backend plan configuration
37 */
38 const getProFieldFeatures = (): Record<string, ProFeatureType> => {
39 const proFeatures = window.IvyForms?.pro?.features?.fields || {}
40 return proFeatures as Record<string, ProFeatureType>
41 }
42
43 /**
44 * Check if a field type is a Pro feature field
45 * Dynamically checks against available Pro features from the plan
46 */
47 const isProFieldFeature = (fieldType: string): boolean => {
48 const proFieldFeatures = getProFieldFeatures()
49 return Object.values(proFieldFeatures).includes(fieldType)
50 }
51
52 /**
53 * Get the feature slug for a given field type
54 * Maps field type to its corresponding feature slug from the plan
55 */
56 const getFeatureSlugForFieldType = (fieldType: string): ProFeatureType | null => {
57 const proFieldFeatures = getProFieldFeatures()
58 for (const [, featureSlug] of Object.entries(proFieldFeatures)) {
59 if (featureSlug === fieldType) {
60 return featureSlug
61 }
62 }
63 return null
64 }
65
66 /**
67 * Get all available Pro field option features from the plan
68 * These are loaded from the backend plan configuration
69 */
70 const getProFieldOptionFeatures = (): Record<string, ProFeatureType> => {
71 const proFeatures = window.IvyForms?.pro?.features?.field_options || {}
72 return proFeatures as Record<string, ProFeatureType>
73 }
74
75 /**
76 * Check if a field option slug is a Pro feature
77 * Dynamically checks against available Pro features from the plan
78 */
79 const isProFieldOption = (slug: string): boolean => {
80 const proFieldOptions = getProFieldOptionFeatures()
81 return Object.values(proFieldOptions).includes(slug)
82 }
83
84 /**
85 * Get all available Pro integration features from the plan
86 * These are loaded from the backend plan configuration
87 */
88 const getProIntegrationFeatures = (): Record<string, ProFeatureType> => {
89 const proFeatures = window.IvyForms?.pro?.features?.integrations || {}
90 return proFeatures as Record<string, ProFeatureType>
91 }
92
93 /**
94 * Check if an integration slug is a Pro feature
95 * Dynamically checks against available Pro features from the plan
96 */
97 const isProIntegration = (slug: string): boolean => {
98 const proIntegrations = getProIntegrationFeatures()
99 return Object.values(proIntegrations).includes(slug)
100 }
101
102 /**
103 * Check if Pro plugin is installed and activated
104 * Pro plugin must be both installed AND activated for this to return true
105 */
106 const isProInstalled = (): boolean => {
107 // First check: Pro scripts must be loaded by WordPress
108 // This is the most reliable indicator that Pro plugin is ACTIVE
109 const proScriptsLoaded =
110 (window as { ivyFormsProScripts?: Record<string, string> }).ivyFormsProScripts !== undefined
111
112 if (!proScriptsLoaded) {
113 return false
114 }
115
116 // Second check: Pro object must exist
117 if (!window.IvyForms?.pro) {
118 return false
119 }
120
121 // Third check: Pro must be loaded or have features
122 const isLoaded = window.IvyForms.pro.loaded === true
123 const hasFeatures =
124 window.IvyForms.pro.features !== undefined &&
125 window.IvyForms.pro.features !== null &&
126 Object.keys(window.IvyForms.pro.features).length > 0
127
128 return isLoaded || hasFeatures
129 }
130
131 /**
132 * Check if Pro license is active
133 * Checks if license settings exist and are valid in the settings store
134 */
135 const isProLicenseActive = (): boolean => {
136 if (!isProInstalled()) return false
137
138 // Check if allSettings has been loaded
139 const settings = settingsStore.allSettings
140 if (!settings) return false
141
142 // Check if Pro license settings exist
143 const proSettings = (settings as Record<string, unknown>).pro as
144 | Record<string, unknown>
145 | undefined
146 if (!proSettings || !proSettings.license) return false
147
148 const licenseInfo = proSettings.license as { status?: string; plan?: string }
149
150 // License is active if status is 'valid' and plan is not 'lite'
151 return licenseInfo.status === 'valid' && licenseInfo.plan !== 'lite'
152 }
153
154 /**
155 * Show upgrade dialog based on current Pro status
156 * @param requiredPlan Optional plan name to show in message (essentials, growth, agency)
157 */
158 const showUpgradeDialog = (requiredPlan?: string) => {
159 const proInstalled = isProInstalled()
160 const licenseActive = isProLicenseActive()
161
162 // Format plan name for display
163 const planName = requiredPlan
164 ? requiredPlan.charAt(0).toUpperCase() + requiredPlan.slice(1)
165 : 'Pro'
166
167 // Scenario 1: Pro plugin NOT installed/active
168 // Show simple "Notification Pop-up" from Figma
169 if (!proInstalled) {
170 const subtitle = requiredPlan
171 ? `${getLabel('pro_plan_feature_available')} ${planName}
172 ${getLabel('pro_plan_feature_available_upgrade')}`
173 : ''
174
175 proUpgradeDialog.showDialog({
176 iconName: 'pro-lightning-alt',
177 title: getLabel('pro_feature_available'),
178 subtitle: subtitle,
179 buttons: {
180 close: {
181 type: 'tertiary',
182 text: getLabel('learn_more'),
183 //TODO: Update URL to specific feature page if available
184 //TODO: Create separate logic for each pages on website
185 function: () => {
186 openPricingPage()
187 },
188 },
189 confirm: {
190 type: 'primary',
191 text: getLabel('upgrade_to_pro'),
192 function: () => {
193 openPricingPage()
194 },
195 },
196 },
197 })
198 }
199 // Scenario 2: Pro installed but license NOT activated
200 // Check if the user's stored plan tier already covers the required plan.
201 // If yes → show "Activate License" dialog (they bought the right plan, just need a key).
202 // If no → show "Upgrade" dialog (they need to purchase a higher tier).
203 else if (!licenseActive) {
204 const pro = useProFeatures()
205 const planHierarchy = ['essentials', 'growth', 'agency']
206 const storedPlanIndex = planHierarchy.indexOf(pro.installedPlan.value)
207 const requiredPlanIndex = planHierarchy.indexOf(requiredPlan || 'essentials')
208 const planCoversFeature = storedPlanIndex !== -1 && storedPlanIndex >= requiredPlanIndex
209
210 // If the user's stored plan does NOT cover the required plan, show upgrade dialog
211 if (requiredPlan && !planCoversFeature) {
212 const subtitle = `${getLabel('pro_plan_feature_available')} ${planName}
213 ${getLabel('pro_plan_feature_available_upgrade')}`
214
215 proUpgradeDialog.showDialog({
216 iconName: 'pro-lightning-alt',
217 title: getLabel('pro_feature_available'),
218 subtitle: subtitle,
219 buttons: {
220 close: {
221 type: 'tertiary',
222 text: getLabel('learn_more'),
223 function: () => {
224 openPricingPage()
225 },
226 },
227 confirm: {
228 type: 'primary',
229 text: getLabel('upgrade_to_pro'),
230 function: () => {
231 openPricingPage()
232 },
233 },
234 },
235 })
236 } else {
237 // Essentials feature - show activation dialog
238 const subtitle = requiredPlan
239 ? `${getLabel('pro_plan_feature_available')} ${planName}
240 ${getLabel('pro_plan_feature_available_upgrade')}`
241 : getLabel('pro_feature_activation_subtitle')
242
243 proUpgradeDialog.showDialog({
244 iconName: 'pro-lightning',
245 title: getLabel('pro_feature_requires_activation'),
246 subtitle: subtitle,
247 buttons: {
248 confirm: {
249 type: 'primary',
250 text: getLabel('activate_license'),
251 function: () => {
252 //TODO: Update URL to specific license activation page if available
253 const currentUrl = new URL(window.location.href)
254 currentUrl.searchParams.set('page', 'ivyforms-settings')
255 currentUrl.hash = '/license'
256 window.location.href = currentUrl.toString()
257 },
258 },
259 },
260 })
261 }
262 }
263 // Scenario 3: Pro active but feature not in current plan - Later for different plan tiers
264 else {
265 const subtitle = requiredPlan
266 ? `${getLabel('pro_plan_feature_available')} ${planName}
267 ${getLabel('pro_plan_feature_available_upgrade')}`
268 : ''
269
270 proUpgradeDialog.showDialog({
271 iconName: 'pro-lightning-alt',
272 title: getLabel('pro_feature_available'),
273 subtitle: subtitle,
274 buttons: {
275 close: {
276 type: 'tertiary',
277 text: getLabel('learn_more'),
278 function: () => {
279 openPricingPage()
280 },
281 },
282 confirm: {
283 type: 'primary',
284 text: getLabel('upgrade_plan'),
285 function: () => {
286 openPricingPage()
287 },
288 },
289 },
290 })
291 }
292 }
293
294 /**
295 * Check if a feature is available and show upgrade dialog if not
296 * @returns true if feature is available, false if upgrade dialog was shown
297 */
298 const checkFeatureAccess = (featureType: ProFeatureType): boolean => {
299 // If Pro is not installed or license is not active, show upgrade dialog
300 if (!isProInstalled() || !isProLicenseActive()) {
301 showUpgradeDialog()
302 return false
303 }
304
305 // Check if specific feature is available (for different plan tiers)
306 const hasFeature = window.IvyForms?.pro?.features?.[featureType]
307 if (!hasFeature) {
308 showUpgradeDialog()
309 return false
310 }
311
312 return true
313 }
314
315 return {
316 isProInstalled,
317 isProLicenseActive,
318 showUpgradeDialog,
319 checkFeatureAccess,
320 getProFieldFeatures,
321 isProFieldFeature,
322 getFeatureSlugForFieldType,
323 getProFieldOptionFeatures,
324 isProFieldOption,
325 getProIntegrationFeatures,
326 isProIntegration,
327 }
328 }
329