Skip to content

feat: migrate frontend from Ziggy to Laravel Wayfinder#1485

Open
herpaderpaldent wants to merge 18 commits into
5.xfrom
web/feat/wayfinder-migration
Open

feat: migrate frontend from Ziggy to Laravel Wayfinder#1485
herpaderpaldent wants to merge 18 commits into
5.xfrom
web/feat/wayfinder-migration

Conversation

@herpaderpaldent
Copy link
Copy Markdown
Contributor

Summary

Replaces Ziggy's global route() function with typed Laravel Wayfinder imports across all 57 Vue files.

Changes

  • vite.config.js — added vite-plugin-run entry to auto-regenerate Wayfinder TypeScript files when routes or controllers change
  • resources/js/app.js — removed ZiggyVue import and plugin registration
  • 57 Vue files — all route('name', params) calls replaced with typed import { fn } from '@/routes/x' + fn(params).url
  • Committed generated Wayfinder filesresources/js/routes/ and resources/js/actions/ are committed so the build works without running wayfinder:generate first

Dead Route Fixes

Three stale route references were corrected:

  • route('acl.edit', role.id)detail(role.id).url (route was renamed to acl.detail)
  • route('acl.update', role.id) in EditGroup.vue → type-specific acl.update.* routes
  • route('update.acl.affiliations', role.id) in ManageControlGroup.vue → correct update route

Notes

  • Build error for vendor/I18n is pre-existing and unrelated to this PR
  • CI test failures due to missing PostgreSQL cache table are pre-existing (same on 5.x)

@herpaderpaldent herpaderpaldent force-pushed the web/feat/wayfinder-migration branch 7 times, most recently from b3dec49 to 7494675 Compare May 6, 2026 18:42
Replace all route() calls across 57 Vue files with typed Wayfinder
imports from @/routes/... TypeScript modules.

Changes:
- vite.config.js: add wayfinder run plugin entry
- app.js: remove ZiggyVue import and .use(ZiggyVue)
- All 57 Vue/JS files: replace route('x.y.z', params) with typed
  function imports, e.g. import { foo } from '@/routes/x/y'

Key migration notes:
- dead routes (acl.edit, acl.update, acl.delete, schedules.delete,
  update.acl.affiliations) mapped to correct Wayfinder equivalents
- type-based ACL update routing uses lookup map in EditGroup.vue
  and ManageControlGroup.vue
- query params wrapped in { query: {...} } for DispatchUpdate,
  DispatchableEntry, EsiAutosuggest, RequiredScopesWarning
- change.main_character: character_id moves from POST body to URL
  path param
- DispatchUpdateButton: removed dead computed url() referencing
  undefined dispatch_transfer_object

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@herpaderpaldent herpaderpaldent force-pushed the web/feat/wayfinder-migration branch from 7494675 to 629bf20 Compare May 6, 2026 18:44
- Add "type": "module" to package.json — all .js files treated as ESM,
  eliminating the CJS Vite Node API deprecation warning
- postcss.config.js and tailwind.config.js converted to ESM
- WebServiceProvider publishes vite.config.js (unchanged filename)

Package version bumps (removed webpack-era dead packages):
- vite: ^4 → ^6.0
- laravel-vite-plugin: ^0.8 → ^1.3
- @vitejs/plugin-vue: ^4 → ^5.2
- vite-plugin-run: ^0.5 → ^0.8
- tailwindcss: ^3.1 → ^3.4
- vue: ^3.0.0-0 → ^3.5
- @headlessui/vue: ^1.0 → ^1.7
- @heroicons/vue: ^2.0 → ^2.1
- @vueuse/core: ^10.0 → ^11.0

Removed: @vue/compiler-sfc, acorn, resolve-url-loader,
  rollup-plugin-copy, sass-loader, vue-loader, vue-template-compiler,
  @babel/plugin-syntax-dynamic-import

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
herpaderpaldent and others added 16 commits May 7, 2026 20:13
Options API components that import route functions from '@/routes/*' need
to expose them via setup() for the template to access them. Without this,
calling eve().url in the template throws '_ctx.eve is not a function'.

15 files updated to return imported route functions from setup().

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ective

- vite.config.js: set startup: true for 'copy vendor' task in monorepo
  context so that 'npm run dev' auto-publishes packages/web assets to root
  without needing a manual vendor:publish
- app.blade.php: remove @routes directive (Ziggy remnant — Wayfinder does
  not inject global route data into the page)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
References existing /img/seat_plus_logo.svg to prevent 404 on /favicon.ico.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ionsUsing

- Upgrade inertiajs/inertia-laravel from ^2.0 to ^3.0
- Upgrade @inertiajs/vue3 from ^1.0 to ^3.0 (JS side)
- Add axios as explicit dependency (no longer bundled with @inertiajs/vue3 v3)
- Remove custom Seatplus\Web\Exception\Handler and ExceptionHandler singleton override
- Register Inertia::handleExceptionsUsing() in WebServiceProvider::boot() using
  the v3 ExceptionResponse API with rootView, withSharedData, and usingMiddleware
- Fix missing trailing commas before setup() in 12 Options API Vue components

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…onsUsing)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Error.vue: add setup() returning home()
- 13 components: inject setup() { return { routeFn } } for Wayfinder imports
  used directly in templates
- ImpersonatingBanner.vue: add setup() returning impersonateStop
- DarkSidebar.vue: move userSettings import to <script setup> block
- ExpandContractComponent.vue: rename conflicting import (details→getContractDetailsUrl)
  to avoid clash with local contractDetails ref
- UserSettings.vue: add logout to existing setup() return

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…-plugin-run startup tasks

- EditSettings.vue had two setup() declarations (original + auto-injected);
  merge enable_esi_search into the existing setup() return
- vite-plugin-run startup tasks (vendor:publish, wayfinder:generate) fired
  concurrently with vite build causing random ENOENT race conditions;
  add build:false so they only run in dev server mode

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
In local dev, skip 500/503 from handleExceptionsUsing so Laravel's
Ignition error page is shown instead of the Vue Error.vue page.
403 and 404 still render via Inertia in all environments.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…for Inertia nav

Inertia v3 removed the v2 modal overlay that showed non-Inertia HTML
(like Ignition) in a popup for Inertia navigation requests. In v3,
Inertia clients receiving raw HTML for an Inertia request show nothing.

Strategy:
- Inertia navigation requests (X-Inertia header): always render Error.vue
  so the user sees a proper error page rather than a broken state.
- Initial page loads in local+debug mode: return null → Ignition renders
  (allows developers to see the full stack trace in the browser).
- Production: all 403/404/500/503 render Error.vue regardless.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…Sidebar

- Replace route(item.route) calls with item.uri (already resolved by SidebarEntries::initializeSidebar())
- Replace route().current() with usePage().props.activeSidebarElement from Inertia shared data
- Remove Ziggy dependency from DarkSidebar entirely

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace all remaining Ziggy route() usages with Wayfinder typed functions
and URLSearchParams/pathname-based navigation:

- useLoadCompleteResource: signature (url, formData) - caller builds URL
- useInfinityScrolling: signature (url, method, postData)
- CharacterContactsComponent: Wayfinder detail() for contact endpoint
- Dashboard/Enlistments + Enlistment: Wayfinder enlistments()/applications()
- SkillQueue + Skills: Wayfinder queue()/skills() from get/character routes
- WalletJournalBalanceChart: Wayfinder corporation/character balance()
- Settings: navTabs now use uri field; isActive() compares pathname
- SeatPlusController::navigation(): adds uri field via route() server-side
- UserList: URLSearchParams for query params, pathname for navigation
- ScopeSettings: pathname.endsWith('/create') for creationMode
- Onboarding: Wayfinder onboarding() with query step param

No more Ziggy/route() dependency in any Vue/JS file.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Drop axios (~14KB gzipped) in favour of a thin native fetch() wrapper.

- Add Functions/apiFetch.js: reads XSRF-TOKEN cookie, sets headers,
  serialises query params, returns parsed JSON body (throws on non-2xx)
- useLoadCompleteResource: CancelToken → AbortController
- useInfinityScrolling: CancelToken.source() → AbortController
- useResolveId, useGetPrice: axios.get → apiFetch
- EveImage, ResolveIdToName, EntityByIdBlock: axios.get → apiFetch
- CharacterFilterModal, MailRepresentation: axios.get → apiFetch
- ExpandContractComponent: axios.get → apiFetch
- Autosuggest, EsiAutosuggest, LocationName: axios.get → apiFetch
- DispatchableEntry: axios.get/post → apiFetch; fix import alias conflict
- DispatchUpdate: axios.post → apiFetch
- Settings: axios.get navigation → native fetch
- HorizonStats: axios.get /queue/status → native fetch
- EditSettings: all three axios usages → apiFetch
- ActivityLogModal, UpdateCharacterComponent: axios → apiFetch
- bootstrap.js: remove axios import and window.axios global
- package.json: remove axios dependency

app.js bundle: 560kB → 518kB (-42kB, ~14kB gzipped saved)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…/js/vendor/

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ent models

fetchAffiliatedCharacterIdsWithRelation() was returning CharacterInfo
models (with eager-loaded relations) which Vue received as objects like
{character_id: 95725047, assets: []}. Vue components expected plain
integers for iteration and API parameters.

Fix: collapse getCharacterIds() to pluck('character_id') directly and
remove the unused $characterRelation eager-loading — no consumer of
this method ever used the loaded relation data.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…entNode crash

Inertia v3's swapComponent calls resetLayoutProps() synchronously before
updating component.value. This triggers a Vue reactive update that tries
to patch the old layout's VNode tree while it may have a null `el`,
causing 'can't access property parentNode, node is null' in componentUpdateFn.

The crash occurs specifically when navigating TO pages with layout:null
(Mail/Index, Error, Onboarding) from layout-wrapped pages (SingleColumnLayout).

Fix: in app.js resolve(), if a page declares layout:null, assign a
TransparentLayout component instead. TransparentLayout is a no-op
passthrough (() => slots.default?.()) that:
- Gives Inertia a valid component to patch between (never null el)
- Still renders the page's own embedded layout (MultiColumnLayout/DarkSidebar)
- Ignores all props so no activeSidebarElement coupling needed

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
headers was imported at module level but not exposed via setup() return,
making it inaccessible in the template as _ctx.headers.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant