PluginProbe
The Innovative Form Builder – IvyForms / 0.9
The Innovative Form Builder – IvyForms v0.9
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 / router / index.ts

index.ts in The Innovative Form Builder – IvyForms 0.9, at frontend/src/router/index.ts

438 lines 13.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import type { RouteRecordRaw } from 'vue-router'
2 import { createWebHashHistory, createRouter } from 'vue-router'
3 import DashboardPage from '../views/admin/dashboard/dashboard-page/DashboardPage.vue'
4 import SettingsPage from '../views/admin/settings/settings-page/SettingsPage.vue'
5 import GeneralSettingsPage from '../views/admin/settings/settings-page/section/general-settings/GeneralSettingsPage.vue'
6 import SecuritySettingsPage from '../views/admin/settings/settings-page/section/security-settings/SecuritySettingsPage.vue'
7 import FormsPage from '../views/admin/forms/forms-page/FormsPage.vue'
8 import FormBuilderPage from '../views/admin/form-builder/form-builder-page/FormBuilderPage.vue'
9 import FormBuilderSettingsPage from '../views/admin/form-builder/form-builder-page/FormBuilderSettingsPage.vue'
10 import FormBuilderManageNotification from '../views/admin/form-builder/form-builder-page/form-settings-notification/FormBuilderManageNotification.vue'
11 import FormBuilderSettings from '../views/admin/form-builder/form-builder-page/form-settings/FormBuilderSettings.vue'
12 import FormBuilderNotificationTable from '../views/admin/form-builder/form-builder-page/form-settings-notification/FormBuilderNotificationTable.vue'
13 import FormBuilderConfirmation from '../views/admin/form-builder/form-builder-page/form-settings-confirmations/FormBuilderConfirmation.vue'
14 import FormBuilderIntegrations from '../views/admin/form-builder/form-builder-page/form-settings-integrations/FormBuilderIntegrations.vue'
15 import EntriesPage from '@/views/admin/entries/entries-page/EntriesPage.vue'
16 import EntryPage from '@/views/admin/entries/entry-page/EntryPage.vue'
17 import FormBuilderResultsEntries from '@/views/admin/form-builder/form-builder-page/form-results/FormBuilderResultsEntries.vue'
18 import FormBuilderResultsPage from '@/views/admin/form-builder/form-builder-page/FormBuilderResultsPage.vue'
19 import {
20 IVYFORMS_FORM_BUILDER_PAGE,
21 IVYFORMS_ALLFORMS_PAGE,
22 IVYFORMS_DASHBOARD_PAGE,
23 IVYFORMS_SETTINGS,
24 IVYFORMS_DASHBOARD,
25 IVYFORMS_ALL_FORMS,
26 IVYFORMS_ADD_FORM,
27 IVYFORMS_EDIT_FORM,
28 IVYFORMS_FORM_SETTING,
29 IVYFORMS_FORM_NOTIFICATIONS,
30 IVYFORMS_NEW_NOTIFICATIONS,
31 IVYFORMS_EDIT_NOTIFICATIONS,
32 IVYFORMS_FORM_CONFIRMATION,
33 IVYFORMS_FORM_INTEGRATIONS,
34 IVYFORMS_ENTRIES,
35 IVYFORMS_ENTRY_DETAILS,
36 IVYFORMS_RESULTS_ENTRY_DETAILS,
37 IVYFORMS_FORM_RESULTS,
38 IVYFORMS_RESULTS,
39 IVYFORMS_ENTRIES_PAGE,
40 IVYFORMS_SETTINGS_PAGE,
41 IVYFORMS_GLOBAL_SETTINGS,
42 IVYFORMS_GENERAL_SETTINGS,
43 IVYFORMS_SECURITY_SETTINGS,
44 IVYFORMS_INTEGRATIONS_PAGE,
45 IVYFORMS_INTEGRATIONS,
46 } from '@/constants/pages.ts'
47 import IntegrationsPage from '@/views/admin/integrations/integrations-page/IntegrationsPage.vue'
48 import { useUnsavedChangesStore } from '@/stores/useUnsavedChangesStore'
49 import { useActionEntityStore } from '@/stores/actionEntityStore'
50 import { useFormBuilder } from '@/stores/useFormBuilder'
51 import { useConfirmationSettingBuilder } from '@/stores/useConfirmationSettingBuilder'
52 import { useNotificationSettingBuilder } from '@/stores/useNotificationSettingBuilder'
53
54 const currentPage = new URLSearchParams(window.location.search).get('page')
55 let routes: RouteRecordRaw[] = []
56
57 if (currentPage === IVYFORMS_DASHBOARD_PAGE) {
58 routes = [
59 {
60 path: '/',
61 name: IVYFORMS_DASHBOARD,
62 component: DashboardPage,
63 meta: {
64 pageType: 'dashboard',
65 breadcrumb: 'Dashboard',
66 },
67 },
68 ]
69 }
70
71 if (currentPage === IVYFORMS_ALLFORMS_PAGE) {
72 routes = [
73 {
74 path: '/',
75 name: IVYFORMS_ALL_FORMS,
76 component: FormsPage,
77 meta: {
78 pageType: 'forms',
79 breadcrumb: 'All Forms',
80 },
81 },
82 ]
83 }
84
85 if (currentPage === IVYFORMS_FORM_BUILDER_PAGE) {
86 routes = [
87 {
88 path: '/',
89 name: IVYFORMS_ADD_FORM,
90 component: FormBuilderPage,
91 meta: {
92 pageType: 'form-builder',
93 breadcrumb: 'Add Form',
94 },
95 },
96 {
97 path: '/manage/:formId(\\d+)',
98 redirect: { name: IVYFORMS_EDIT_FORM },
99 children: [
100 {
101 path: '',
102 name: IVYFORMS_EDIT_FORM,
103 component: FormBuilderPage,
104 meta: {
105 pageType: 'form-builder',
106 breadcrumb: 'Edit Form',
107 requiresForm: true,
108 },
109 },
110 {
111 path: 'settings',
112 name: IVYFORMS_SETTINGS,
113 component: FormBuilderSettingsPage,
114 meta: {
115 pageType: 'form-settings',
116 breadcrumb: 'Settings',
117 requiresForm: true,
118 },
119 children: [
120 {
121 path: 'general',
122 name: IVYFORMS_FORM_SETTING,
123 component: FormBuilderSettings,
124 meta: {
125 breadcrumb: 'General',
126 requiresForm: true,
127 },
128 },
129 {
130 path: 'notification',
131 name: IVYFORMS_FORM_NOTIFICATIONS,
132 component: FormBuilderNotificationTable,
133 meta: {
134 breadcrumb: 'Notifications',
135 requiresForm: true,
136 },
137 },
138 {
139 path: 'notification/manage',
140 name: IVYFORMS_NEW_NOTIFICATIONS,
141 component: FormBuilderManageNotification,
142 meta: {
143 breadcrumb: 'New Notification',
144 requiresForm: true,
145 },
146 },
147 {
148 path: 'notification/manage/:notificationId(\\d+)',
149 name: IVYFORMS_EDIT_NOTIFICATIONS,
150 component: FormBuilderManageNotification,
151 meta: {
152 breadcrumb: 'Edit Notification',
153 requiresForm: true,
154 },
155 },
156 {
157 path: 'confirmation/manage/:confirmationId(\\d+)',
158 name: IVYFORMS_FORM_CONFIRMATION,
159 component: FormBuilderConfirmation,
160 meta: {
161 breadcrumb: 'Confirmation',
162 requiresForm: true,
163 },
164 },
165 {
166 path: 'integrations',
167 redirect: 'integrations/wpdatatables',
168 },
169 {
170 path: 'integrations/:integration',
171 name: IVYFORMS_FORM_INTEGRATIONS,
172 component: FormBuilderIntegrations,
173 meta: {
174 breadcrumb: 'Integrations',
175 requiresForm: true,
176 },
177 },
178 ],
179 },
180 {
181 path: 'results',
182 name: IVYFORMS_RESULTS,
183 component: FormBuilderResultsPage,
184 meta: {
185 pageType: 'form-results',
186 breadcrumb: 'Results',
187 requiresForm: true,
188 },
189 children: [
190 {
191 path: 'entries',
192 name: IVYFORMS_FORM_RESULTS,
193 component: FormBuilderResultsEntries,
194 meta: {
195 breadcrumb: 'Entries',
196 requiresForm: true,
197 },
198 },
199 ],
200 },
201 {
202 path: 'results/entries/details/:entryId(\\d+)',
203 name: IVYFORMS_RESULTS_ENTRY_DETAILS,
204 component: EntryPage,
205 meta: {
206 breadcrumb: 'Entry Details',
207 requiresForm: true,
208 returnContext: 'form-results',
209 },
210 },
211 ],
212 },
213 ]
214 }
215
216 if (currentPage === IVYFORMS_ENTRIES_PAGE) {
217 routes = [
218 {
219 path: '/',
220 name: IVYFORMS_ENTRIES,
221 component: EntriesPage,
222 meta: {
223 pageType: 'entries',
224 breadcrumb: 'All Entries',
225 },
226 },
227 {
228 path: '/details/:id(\\d+)',
229 name: IVYFORMS_ENTRY_DETAILS,
230 component: EntryPage,
231 meta: {
232 breadcrumb: 'Entry Details',
233 returnContext: 'all-entries',
234 },
235 },
236 ]
237 }
238 if (currentPage === IVYFORMS_SETTINGS_PAGE) {
239 routes = [
240 {
241 path: '/',
242 name: IVYFORMS_GLOBAL_SETTINGS,
243 component: SettingsPage,
244 redirect: '/general',
245 meta: {
246 pageType: 'settings',
247 breadcrumb: 'Settings',
248 },
249 children: [
250 {
251 path: 'general',
252 name: IVYFORMS_GENERAL_SETTINGS,
253 component: GeneralSettingsPage,
254 meta: {
255 breadcrumb: 'General',
256 },
257 },
258 {
259 path: 'security',
260 redirect: 'security/recaptcha',
261 },
262 {
263 path: 'security/:provider(recaptcha|hcaptcha|turnstile|honeypot)',
264 name: IVYFORMS_SECURITY_SETTINGS,
265 component: SecuritySettingsPage,
266 meta: {
267 breadcrumb: 'Security',
268 },
269 },
270 ],
271 },
272 ]
273 }
274
275 if (currentPage === IVYFORMS_INTEGRATIONS_PAGE) {
276 routes = [
277 {
278 path: '/',
279 name: IVYFORMS_INTEGRATIONS,
280 component: IntegrationsPage,
281 meta: {
282 pageType: 'integrations',
283 breadcrumb: 'Integrations',
284 },
285 },
286 ]
287 }
288
289 const router = createRouter({
290 history: createWebHashHistory(),
291 routes,
292 scrollBehavior(to, from, savedPosition) {
293 // Restore scroll position on browser back/forward
294 if (savedPosition) {
295 return savedPosition
296 }
297 // Scroll to top on new navigation
298 return { top: 0 }
299 },
300 })
301
302 // Wrap addRoute to prevent warnings when parent route doesn't exist
303 // This happens when Pro plugin tries to add routes to settings page when we're on a different page
304 const originalAddRoute = router.addRoute.bind(router)
305 router.addRoute = function (parentOrRoute: string | RouteRecordRaw, route?: RouteRecordRaw) {
306 if (typeof parentOrRoute === 'string' && route) {
307 // Only call with (string, RouteRecordRaw)
308 if (!router.hasRoute(parentOrRoute)) {
309 return undefined as unknown
310 }
311 return originalAddRoute(parentOrRoute, route)
312 } else if (typeof parentOrRoute !== 'string' && !route) {
313 // Only call with (RouteRecordRaw)
314 return originalAddRoute(parentOrRoute)
315 }
316 // Do not call originalAddRoute with invalid argument combinations
317 return undefined as unknown
318 } as typeof router.addRoute
319
320 // Make router available globally for Pro plugin and other extensions to add routes dynamically
321 if (typeof window !== 'undefined') {
322 ;(
323 window as unknown as {
324 IvyFormsRouter?: typeof router
325 IvyFormsAddRoute?: (parentName: string, route: RouteRecordRaw) => boolean
326 }
327 ).IvyFormsRouter = router
328 ;(
329 window as unknown as {
330 IvyFormsRouter?: typeof router
331 IvyFormsAddRoute?: (parentName: string, route: RouteRecordRaw) => boolean
332 }
333 ).IvyFormsAddRoute = (parentName: string, route: RouteRecordRaw) => {
334 if (router.hasRoute(parentName)) {
335 router.addRoute(parentName, route)
336 return true
337 } else {
338 return false
339 }
340 }
341 }
342
343 // Navigation guard for unsaved changes
344 let isFirstNavigation = true
345 let skipNextGuard = false
346
347 // Define which routes use which stores
348 const formBuilderRoutes = [
349 IVYFORMS_ADD_FORM,
350 IVYFORMS_EDIT_FORM,
351 IVYFORMS_FORM_SETTING,
352 IVYFORMS_FORM_CONFIRMATION,
353 IVYFORMS_FORM_INTEGRATIONS,
354 ]
355 const confirmationRoutes = [IVYFORMS_FORM_CONFIRMATION]
356 const notificationRoutes = [
357 IVYFORMS_FORM_NOTIFICATIONS,
358 IVYFORMS_NEW_NOTIFICATIONS,
359 IVYFORMS_EDIT_NOTIFICATIONS,
360 ]
361
362 router.beforeEach((to, from, next) => {
363 // Skip guard if we're navigating after user confirmed in dialog
364 if (skipNextGuard) {
365 skipNextGuard = false
366 next()
367 return
368 }
369
370 // Skip unsaved changes check on initial page load
371 if (isFirstNavigation) {
372 isFirstNavigation = false
373 next()
374 return
375 }
376
377 // Skip unsaved changes check when navigating from entry details pages
378 // These pages temporarily load forms for viewing only
379 if (from.name === IVYFORMS_ENTRY_DETAILS || from.name === IVYFORMS_RESULTS_ENTRY_DETAILS) {
380 next()
381 return
382 }
383
384 const unsavedChangesStore = useUnsavedChangesStore()
385 const formBuilderStore = useFormBuilder()
386 const confirmationStore = useConfirmationSettingBuilder()
387 const notificationStore = useNotificationSettingBuilder()
388 const actionEntityStore = useActionEntityStore()
389
390 // Only check stores that are relevant to the route we're leaving from
391 const entitiesToCheck: ('formBuilder' | 'confirmationBuilder' | 'notificationBuilder')[] = []
392
393 // Check form builder only if:
394 // 1. We're leaving a form builder route
395 // 2. AND the form builder is actually editing a form (not just initialized with defaults)
396 if (formBuilderRoutes.includes(from.name as string) && formBuilderStore.isEditing) {
397 entitiesToCheck.push('formBuilder')
398 }
399
400 // Check confirmation store only if we're leaving a confirmation route AND actively editing
401 if (confirmationRoutes.includes(from.name as string) && confirmationStore.isEditing) {
402 entitiesToCheck.push('confirmationBuilder')
403 }
404
405 // Check notification store only if we're leaving a notification route AND actively editing
406 if (notificationRoutes.includes(from.name as string) && notificationStore.isEditing) {
407 entitiesToCheck.push('notificationBuilder')
408 }
409
410 // If no entities need to be checked, proceed immediately
411 if (entitiesToCheck.length === 0) {
412 next()
413 return
414 }
415
416 // Check if there are any unsaved changes in the relevant stores using new dirty flag approach
417 if (unsavedChangesStore.hasAnyUnsavedChanges(entitiesToCheck)) {
418 // Show confirmation dialog
419 actionEntityStore.handleActionClick(null, null, 'form', 'unsaved_changes', {}, () => {
420 // Clear the dirty flags for the entities we checked
421 entitiesToCheck.forEach((entity) => unsavedChangesStore.markClean(entity))
422 // Set flag to skip guard on the next navigation
423 skipNextGuard = true
424 // User confirmed - manually navigate to the target route
425 router.push(to)
426 })
427
428 // Cancel the current navigation
429 next(false)
430 return
431 }
432
433 // No unsaved changes - proceed immediately
434 next()
435 })
436
437 export default router
438