| 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 |
|