PluginProbe
The Innovative Form Builder – IvyForms / 1.0
The Innovative Form Builder – IvyForms v1.0
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 / views / admin / integrations / integrations-page / IntegrationsSectionContent.vue

IntegrationsSectionContent.vue in The Innovative Form Builder – IvyForms 1.0, at frontend/src/views/admin/integrations/integrations-page/IntegrationsSectionContent.vue

391 lines 13.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <template>
2 <div class="ivyforms-integrations-section-content">
3 <div
4 class="ivyforms-integrations-section-content__header ivyforms-flex ivyforms-flex-direction-column ivyforms-align-items-start ivyforms-gap-8"
5 >
6 <div class="ivyforms-integrations-section-content__title medium-18">
7 {{ getLabel('integrations_section_title') }}
8 </div>
9 <div class="ivyforms-integrations-section-content__body regular-16">
10 {{ getLabel('integrations_section_body') }}
11 </div>
12 <div
13 class="ivyforms-integrations-section-content__option-bar ivyforms-flex ivyforms-justify-content-between ivyforms-align-items-start"
14 >
15 <IvySearch
16 v-model="searchQuery"
17 :placeholder="getLabel('search')"
18 class="ivyforms-integrations-section-content__search-fixed-width"
19 />
20 <IvyTabs
21 v-model="activeCategory"
22 :tabs="categoryTabs"
23 tab-style="fill"
24 priority="tertiary"
25 background
26 />
27 </div>
28 </div>
29 <div
30 class="ivyforms-integrations-section-content__list ivyforms-flex ivyforms-flex-direction-row ivyforms-align-items-start ivyforms-gap-16 ivyforms-pt-24"
31 >
32 <!-- Skeleton loaders while loading -->
33 <template v-if="isLoading">
34 <el-skeleton
35 v-for="n in skeletonCount"
36 :key="n"
37 :rows="3"
38 animated
39 class="ivyforms-integration-skeleton"
40 />
41 </template>
42
43 <!-- Actual integration cards -->
44 <IvyIntegrationCard
45 v-for="integration in filteredIntegrations"
46 v-else
47 :key="integration.value"
48 :title="getLabel(integration.name)"
49 :description="getLabel(integration.description)"
50 :image="integration.image"
51 :type="getIntegrationType(integration)"
52 :required-plan="integration.requiredPlan"
53 :has-global-settings="integration.hasGlobalSettings"
54 :toggle-value="getIntegrationToggleValue(integration.value)"
55 :toggle-disabled="isIntegrationToggleDisabled(integration.value)"
56 :is-loading="isInstalling === integration.value"
57 :button-text="getButtonText(integration.value)"
58 :on-button-click="() => handleButtonClick(integration.value)"
59 :on-toggle-change="(val) => handleToggleChange(integration.value, val)"
60 />
61 </div>
62 </div>
63 </template>
64
65 <script setup lang="ts">
66 import { ref, computed, onMounted } from 'vue'
67 import IvyMessage from '@/views/_components/message/ivyMessage'
68 import { useLabels } from '@/composables/useLabels'
69 import { useIntegrations } from '@/composables/useIntegrations'
70 import { useProFeatures } from '@/composables/useProFeatures'
71 import { useProFeatureUpgrade } from '@/composables/useProFeatureUpgrade'
72 import { useSettingsStore } from '@/stores/useSettingsStore'
73 import { useApiClient } from '@/composables/useApiClient'
74 import IvySearch from '@/views/_components/search/IvySearch.vue'
75 import IvyTabs from '@/views/_components/tabs/IvyTabs.vue'
76
77 const { getLabel } = useLabels()
78 const settingsStore = useSettingsStore()
79 const pro = useProFeatures()
80 const {
81 checkFeatureAccess,
82 getProIntegrationFeatures,
83 showUpgradeDialog,
84 isProInstalled,
85 isProLicenseActive,
86 } = useProFeatureUpgrade()
87 const {
88 integrations,
89 getIntegrationType,
90 loadIntegrationStates,
91 loadIntegrations,
92 integrationState,
93 } = useIntegrations()
94 const apiClient = useApiClient()
95 const searchQuery = ref('')
96 const activeCategory = ref('all')
97 const isLoading = ref(true)
98 const isInstalling = ref<string | null>(null) // Track which plugin is being installed
99 const skeletonCount = ref(2) // Default skeleton count, will be updated after loading
100
101 // Get all available Pro integrations (mailchimp, wpdatatables, etc.)
102 const proIntegrations = computed(() => getProIntegrationFeatures())
103
104 const categoryTabs = [
105 { name: 'all', label: getLabel('all') },
106 { name: 'lite', label: getLabel('lite') },
107 { name: 'pro', label: getLabel('pro') },
108 { name: 'soon', label: getLabel('soon') },
109 ]
110
111 // Load all settings on mount
112 onMounted(async () => {
113 isLoading.value = true
114 await pro.fetch() // Fetch Pro features first to determine plan
115 await settingsStore.loadAllSettings()
116 await loadIntegrations() // Load integrations from backend registry
117 await loadIntegrationStates()
118
119 // Update skeleton count based on actual integrations loaded
120 skeletonCount.value = integrations.value.length || 2
121
122 isLoading.value = false
123 })
124
125 // Get toggle value based on integration state
126 const getIntegrationToggleValue = (integrationValue: string): boolean => {
127 const integrationSettings = settingsStore.integrationsSettings?.[integrationValue]
128 const state = integrationState.value?.[integrationValue]
129
130 const isWpDataTables = integrationValue === 'wpdatatables'
131 if (isWpDataTables && state && !state.connected) {
132 return false
133 }
134
135 return integrationSettings?.enabled || false
136 }
137
138 // Check if toggle should be disabled
139 const isIntegrationToggleDisabled = (integrationValue: string): boolean => {
140 const integration = integrations.value.find((i) => i.value === integrationValue)
141 if (!integration) return false
142
143 // If Pro is installed but license is NOT active, disable the toggle
144 if (isProInstalled() && !isProLicenseActive()) {
145 return true
146 }
147
148 return false
149 }
150
151 // Get button text for integrations
152 const getButtonText = (integrationValue: string): string | undefined => {
153 const integration = integrations.value.find((i) => i.value === integrationValue)
154 if (!integration) return undefined
155
156 const currentType = getIntegrationType(integration)
157
158 // For 'proDisabled' type, check if license is active
159 // If Pro installed but license NOT active, show "Activate License"
160 if (currentType === 'proDisabled') {
161 // If Pro is installed but license NOT active, show "Activate License"
162 if (isProInstalled() && !isProLicenseActive()) {
163 return getLabel('activate_license')
164 }
165 // Otherwise, let the default logic handle it (Learn More / Go to Settings)
166 }
167
168 // Special handling for wpDataTables
169 if (integrationValue === 'wpdatatables') {
170 const integrationSettings = settingsStore.integrationsSettings?.[integrationValue]
171 const isEnabled = integrationSettings?.enabled || false
172 const isConnected = integrationSettings?.connected || false
173
174 // If currently installing, show "Installing..."
175 if (isInstalling.value === integrationValue) {
176 return getLabel('installing')
177 }
178
179 // If installed and toggle is ON, show "Create Tables"
180 if (isConnected && isEnabled) {
181 return getLabel('create_tables')
182 }
183 }
184 return undefined // Use default button text from card
185 }
186
187 // Handle button click (Install/Learn More/Go to Settings/Upgrade/Create Tables)
188 const handleButtonClick = async (integrationValue: string) => {
189 const integration = integrations.value.find((i) => i.value === integrationValue)
190 if (!integration) return
191
192 const currentType = getIntegrationType(integration)
193
194 // Handle Upgrade button for Pro integrations when Pro is not installed or plan is insufficient
195 if (currentType === 'upgrade') {
196 // Show upgrade/activation dialog with plan requirement
197 showUpgradeDialog(integration.requiredPlan)
198 return
199 }
200
201 // Handle Install button for disabled integrations
202 if (currentType === 'disabled') {
203 // For wpDataTables, install and activate the plugin
204 if (integrationValue === 'wpdatatables') {
205 try {
206 isInstalling.value = integrationValue
207
208 const response = await apiClient.request('/integrations/install-plugin', {
209 method: 'POST',
210 data: {
211 plugin_slug: 'wpdatatables',
212 },
213 })
214
215 if (response.data?.success) {
216 // Show success message
217 IvyMessage({
218 message: getLabel('wpdatatables_installed_successfully'),
219 type: 'success',
220 duration: 3000,
221 })
222
223 await settingsStore.updateIntegrationEnabled(integrationValue, true)
224
225 // Reload settings to update the connected status
226 await settingsStore.loadAllSettings()
227 await loadIntegrationStates()
228 } else {
229 IvyMessage({
230 message: response.data?.message || getLabel('installation_failed'),
231 type: 'error',
232 duration: 3000,
233 })
234 }
235 } catch (error) {
236 console.error('Error installing plugin:', error)
237 IvyMessage({
238 message: `${getLabel('installation_failed')} ${error}`,
239 type: 'error',
240 duration: 3000,
241 })
242 } finally {
243 isInstalling.value = null
244 }
245 return
246 }
247
248 // For other integrations, just enable them
249 await settingsStore.updateIntegrationEnabled(integrationValue, true)
250 await loadIntegrationStates() // Reload to update UI
251 return
252 }
253
254 // Handle active Lite integrations
255 if (currentType === 'active') {
256 // For wpDataTables with toggle ON, navigate to constructor
257 if (integrationValue === 'wpdatatables') {
258 const integrationSettings = settingsStore.integrationsSettings?.[integrationValue]
259 const isEnabled = integrationSettings?.enabled || false
260
261 if (isEnabled) {
262 // Toggle ON: Open wpDataTables constructor in new tab
263 window.open('admin.php?page=wpdatatables-constructor&source=ivyforms', '_blank')
264 return
265 } else {
266 // Toggle OFF: Open documentation in new tab
267 window.open('https://ivyforms.com/documentation/integration-with-wpdatatables/', '_blank')
268 return
269 }
270 }
271
272 // For other integrations, open Learn More URL if available
273 if (integration.learnMoreUrl) {
274 window.open(integration.learnMoreUrl, '_blank')
275 }
276 return
277 }
278
279 // Handle Go to Settings for Pro integrations (proActive/proDisabled)
280 if (currentType === 'proActive' || currentType === 'proDisabled') {
281 // If Pro is installed but license NOT active, show activation dialog
282 if (isProInstalled() && !isProLicenseActive()) {
283 showUpgradeDialog(integration.requiredPlan)
284 return
285 }
286
287 // Check if this integration is a Pro feature that requires access verification
288 const integrationFeatureSlug = `${integrationValue}_integration`
289 if (Object.values(proIntegrations.value).includes(integrationFeatureSlug)) {
290 if (!checkFeatureAccess(integrationFeatureSlug)) {
291 // checkFeatureAccess will show the appropriate upgrade dialog
292 return
293 }
294 }
295
296 // Check if toggle is ON - only navigate if enabled AND has global settings
297 const integrationSettings = settingsStore.integrationsSettings?.[integrationValue]
298 if (integrationSettings?.enabled && integration.hasGlobalSettings !== false) {
299 // Navigate to settings page with integration route
300 // Use relative path which works better in WordPress admin
301 const currentUrl = new URL(window.location.href)
302 currentUrl.searchParams.set('page', 'ivyforms-settings')
303 currentUrl.hash = `/integrations/${integrationValue}`
304 window.location.href = currentUrl.toString()
305 } else {
306 // Toggle OFF or no global settings: Open Learn More URL if available
307 if (integration.learnMoreUrl) {
308 window.open(integration.learnMoreUrl, '_blank')
309 }
310 }
311 return
312 }
313 }
314
315 // Handle toggle change
316 const handleToggleChange = async (integrationValue: string, enabled: boolean) => {
317 // Check if this integration is a Pro feature and user is trying to enable it
318 const integrationFeatureSlug = `${integrationValue}_integration`
319 if (enabled && Object.values(proIntegrations.value).includes(integrationFeatureSlug)) {
320 // Check if user has access to this Pro integration
321 if (!checkFeatureAccess(integrationFeatureSlug)) {
322 // checkFeatureAccess will show the appropriate upgrade dialog
323 return
324 }
325 }
326
327 await settingsStore.updateIntegrationEnabled(integrationValue, enabled)
328 // Reload ALL settings from server to get fresh 'connected' status
329 await settingsStore.loadAllSettings()
330 await loadIntegrationStates() // Reload to update UI
331 }
332
333 const filteredIntegrations = computed(() => {
334 let result = integrations.value
335 if (activeCategory.value !== 'all') {
336 result = result.filter((integration) => {
337 // Map Pro plan levels to 'pro' category for filtering
338 const isPro = ['essentials', 'growth', 'agency'].includes(integration.searchCategory)
339
340 if (activeCategory.value === 'pro') {
341 return isPro
342 } else if (activeCategory.value === 'lite') {
343 return integration.searchCategory === 'lite'
344 } else if (activeCategory.value === 'soon') {
345 return integration.type === 'soon'
346 }
347
348 return true
349 })
350 }
351 if (searchQuery.value) {
352 const q = searchQuery.value.toLowerCase()
353 result = result.filter(
354 (integration) =>
355 getLabel(integration.name).toLowerCase().includes(q) ||
356 getLabel(integration.description).toLowerCase().includes(q),
357 )
358 }
359 return result
360 })
361 </script>
362
363 <style scoped lang="scss">
364 .ivyforms-integrations-section-content {
365 &__title {
366 color: var(--map-base-text-0);
367 }
368 &__body {
369 color: var(--map-base-text--1);
370 }
371 &__option-bar {
372 align-self: stretch;
373 }
374 // Search
375 &__search-fixed-width {
376 width: 309px;
377 min-width: 309px;
378 max-width: 309px;
379 }
380 }
381
382 .ivyforms-integration-skeleton {
383 width: 394px;
384 height: 172px;
385 padding: 24px;
386 border-radius: 8px;
387 background: var(--map-ground-level-1-foreground);
388 border: 1px solid var(--map-base-dusk-stroke--2);
389 }
390 </style>
391