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 / router / index.ts

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

439 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 children: [],
178 },
179 ],
180 },
181 {
182 path: 'results',
183 name: IVYFORMS_RESULTS,
184 component: FormBuilderResultsPage,
185 meta: {
186 pageType: 'form-results',
187 breadcrumb: 'Results',
188 requiresForm: true,
189 },
190 children: [
191 {
192 path: 'entries',
193 name: IVYFORMS_FORM_RESULTS,
194 component: FormBuilderResultsEntries,
195 meta: {
196 breadcrumb: 'Entries',
197 requiresForm: true,
198 },
199 },
200 ],
201 },
202 {
203 path: 'results/entries/details/:entryId(\\d+)',
204 name: IVYFORMS_RESULTS_ENTRY_DETAILS,
205 component: EntryPage,
206 meta: {
207 breadcrumb: 'Entry Details',
208 requiresForm: true,
209 returnContext: 'form-results',
210 },
211 },
212 ],
213 },
214 ]
215 }
216
217 if (currentPage === IVYFORMS_ENTRIES_PAGE) {
218 routes = [
219 {
220 path: '/',
221 name: IVYFORMS_ENTRIES,
222 component: EntriesPage,
223 meta: {
224 pageType: 'entries',
225 breadcrumb: 'All Entries',
226 },
227 },
228 {
229 path: '/details/:id(\\d+)',
230 name: IVYFORMS_ENTRY_DETAILS,
231 component: EntryPage,
232 meta: {
233 breadcrumb: 'Entry Details',
234 returnContext: 'all-entries',
235 },
236 },
237 ]
238 }
239 if (currentPage === IVYFORMS_SETTINGS_PAGE) {
240 routes = [
241 {
242 path: '/',
243 name: IVYFORMS_GLOBAL_SETTINGS,
244 component: SettingsPage,
245 redirect: '/general',
246 meta: {
247 pageType: 'settings',
248 breadcrumb: 'Settings',
249 },
250 children: [
251 {
252 path: 'general',
253 name: IVYFORMS_GENERAL_SETTINGS,
254 component: GeneralSettingsPage,
255 meta: {
256 breadcrumb: 'General',
257 },
258 },
259 {
260 path: 'security',
261 redirect: 'security/recaptcha',
262 },
263 {
264 path: 'security/:provider(recaptcha|hcaptcha|turnstile|honeypot)',
265 name: IVYFORMS_SECURITY_SETTINGS,
266 component: SecuritySettingsPage,
267 meta: {
268 breadcrumb: 'Security',
269 },
270 },
271 ],
272 },
273 ]
274 }
275
276 if (currentPage === IVYFORMS_INTEGRATIONS_PAGE) {
277 routes = [
278 {
279 path: '/',
280 name: IVYFORMS_INTEGRATIONS,
281 component: IntegrationsPage,
282 meta: {
283 pageType: 'integrations',
284 breadcrumb: 'Integrations',
285 },
286 },
287 ]
288 }
289
290 const router = createRouter({
291 history: createWebHashHistory(),
292 routes,
293 scrollBehavior(to, from, savedPosition) {
294 // Restore scroll position on browser back/forward
295 if (savedPosition) {
296 return savedPosition
297 }
298 // Scroll to top on new navigation
299 return { top: 0 }
300 },
301 })
302
303 // Wrap addRoute to prevent warnings when parent route doesn't exist
304 // This happens when Pro plugin tries to add routes to settings page when we're on a different page
305 const originalAddRoute = router.addRoute.bind(router)
306 router.addRoute = function (parentOrRoute: string | RouteRecordRaw, route?: RouteRecordRaw) {
307 if (typeof parentOrRoute === 'string' && route) {
308 // Only call with (string, RouteRecordRaw)
309 if (!router.hasRoute(parentOrRoute)) {
310 return undefined as unknown
311 }
312 return originalAddRoute(parentOrRoute, route)
313 } else if (typeof parentOrRoute !== 'string' && !route) {
314 // Only call with (RouteRecordRaw)
315 return originalAddRoute(parentOrRoute)
316 }
317 // Do not call originalAddRoute with invalid argument combinations
318 return undefined as unknown
319 } as typeof router.addRoute
320
321 // Make router available globally for Pro plugin and other extensions to add routes dynamically
322 if (typeof window !== 'undefined') {
323 ;(
324 window as unknown as {
325 IvyFormsRouter?: typeof router
326 IvyFormsAddRoute?: (parentName: string, route: RouteRecordRaw) => boolean
327 }
328 ).IvyFormsRouter = router
329 ;(
330 window as unknown as {
331 IvyFormsRouter?: typeof router
332 IvyFormsAddRoute?: (parentName: string, route: RouteRecordRaw) => boolean
333 }
334 ).IvyFormsAddRoute = (parentName: string, route: RouteRecordRaw) => {
335 if (router.hasRoute(parentName)) {
336 router.addRoute(parentName, route)
337 return true
338 } else {
339 return false
340 }
341 }
342 }
343
344 // Navigation guard for unsaved changes
345 let isFirstNavigation = true
346 let skipNextGuard = false
347
348 // Define which routes use which stores
349 const formBuilderRoutes = [
350 IVYFORMS_ADD_FORM,
351 IVYFORMS_EDIT_FORM,
352 IVYFORMS_FORM_SETTING,
353 IVYFORMS_FORM_CONFIRMATION,
354 IVYFORMS_FORM_INTEGRATIONS,
355 ]
356 const confirmationRoutes = [IVYFORMS_FORM_CONFIRMATION]
357 const notificationRoutes = [
358 IVYFORMS_FORM_NOTIFICATIONS,
359 IVYFORMS_NEW_NOTIFICATIONS,
360 IVYFORMS_EDIT_NOTIFICATIONS,
361 ]
362
363 router.beforeEach((to, from, next) => {
364 // Skip guard if we're navigating after user confirmed in dialog
365 if (skipNextGuard) {
366 skipNextGuard = false
367 next()
368 return
369 }
370
371 // Skip unsaved changes check on initial page load
372 if (isFirstNavigation) {
373 isFirstNavigation = false
374 next()
375 return
376 }
377
378 // Skip unsaved changes check when navigating from entry details pages
379 // These pages temporarily load forms for viewing only
380 if (from.name === IVYFORMS_ENTRY_DETAILS || from.name === IVYFORMS_RESULTS_ENTRY_DETAILS) {
381 next()
382 return
383 }
384
385 const unsavedChangesStore = useUnsavedChangesStore()
386 const formBuilderStore = useFormBuilder()
387 const confirmationStore = useConfirmationSettingBuilder()
388 const notificationStore = useNotificationSettingBuilder()
389 const actionEntityStore = useActionEntityStore()
390
391 // Only check stores that are relevant to the route we're leaving from
392 const entitiesToCheck: ('formBuilder' | 'confirmationBuilder' | 'notificationBuilder')[] = []
393
394 // Check form builder only if:
395 // 1. We're leaving a form builder route
396 // 2. AND the form builder is actually editing a form (not just initialized with defaults)
397 if (formBuilderRoutes.includes(from.name as string) && formBuilderStore.isEditing) {
398 entitiesToCheck.push('formBuilder')
399 }
400
401 // Check confirmation store only if we're leaving a confirmation route AND actively editing
402 if (confirmationRoutes.includes(from.name as string) && confirmationStore.isEditing) {
403 entitiesToCheck.push('confirmationBuilder')
404 }
405
406 // Check notification store only if we're leaving a notification route AND actively editing
407 if (notificationRoutes.includes(from.name as string) && notificationStore.isEditing) {
408 entitiesToCheck.push('notificationBuilder')
409 }
410
411 // If no entities need to be checked, proceed immediately
412 if (entitiesToCheck.length === 0) {
413 next()
414 return
415 }
416
417 // Check if there are any unsaved changes in the relevant stores using new dirty flag approach
418 if (unsavedChangesStore.hasAnyUnsavedChanges(entitiesToCheck)) {
419 // Show confirmation dialog
420 actionEntityStore.handleActionClick(null, null, 'form', 'unsaved_changes', {}, () => {
421 // Clear the dirty flags for the entities we checked
422 entitiesToCheck.forEach((entity) => unsavedChangesStore.markClean(entity))
423 // Set flag to skip guard on the next navigation
424 skipNextGuard = true
425 // User confirmed - manually navigate to the target route
426 router.push(to)
427 })
428
429 // Cancel the current navigation
430 next(false)
431 return
432 }
433
434 // No unsaved changes - proceed immediately
435 next()
436 })
437
438 export default router
439