PluginProbe
The Innovative Form Builder – IvyForms / 0.7
The Innovative Form Builder – IvyForms v0.7
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 0.7, at frontend/src/views/admin/integrations/integrations-page/IntegrationsSectionContent.vue

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