WS-3 PR-B1: Portal moves + routing wiring #4

Merged
bert.hausmans merged 11 commits from feat/ws-3-pr-b1-portal-moves into main 2026-05-05 20:21:05 +02:00

Summary

Single-SPA consolidation, stage B part 1: moves apps/portal/ content into
apps/app/, wires routes with explicit meta.layout + meta.context,
deletes apps/portal/ entirely. Auth refactor explicitly deferred to PR-B2.

Refs: ARCH-CONSOLIDATION-2026-04.md §4.2 + §5 (WS-3), PR-A in main.

What landed

  • 10 portal pages moved to apps/app/src/pages/{portal,register}/
  • 25 portal components moved to apps/app/src/components/{shared/public-form,portal}/
  • 6 byte-equivalent duplicate components deleted instead of moved
  • packages/form-schema/ inlined under apps/app/src/{types,utils,composables}/forms/
  • Portal stores moved + auth store renamed to usePortalAuthStore (collision avoidance)
  • Single carve-out in guards.ts: if (to.meta.context === 'portal') return
  • ESLint boundaries sub-zones activated (TECH-WS3-BOUNDARIES-SUBZONES)
  • apps/portal/ deleted entirely (-54k LOC, +5k LOC under apps/app/)
  • register/success.vue cleaned of auth-store dependency (relies on query param only)

Verification

  • Lint: 0 errors, 0 warnings (beyond pre-existing boundaries v5→v6 deprecation warnings)
  • Typecheck: 0 errors
  • Frontend tests: 162 passed (up from 73)
  • Backend tests: 1486 passed (unchanged)
  • Paper smoke: portal-routes, register-routes, organizer-routes, platform-routes all resolve correctly

What is explicitly NOT in this PR (→ PR-B2)

  • Auth-store merge (currently useAuthStore + usePortalAuthStore co-exist)
  • Dual-axios refactor
  • Route guards beyond requiresAuth and the meta-context carve-out
  • Context-switcher
  • Post-login landing logic
  • A13-3 open-redirect fix
  • A13-8 sessionStorage migration
  • CookieBearerToken backend simplification
  • DirectAdmin deploy config single-hostname

Adaptations from the original prompt

  1. No pnpm-workspace setup — Phase F skipped workspace.yaml + root scripts edits
  2. packages/form-schema/ was alias-only (no package.json); deletion via inline + drop alias
  3. Route-guard carve-out via meta.context === 'portal' (1 if-block)
  4. Register pages use meta.public: true (apps/app convention) + meta.layout: 'PublicLayout'
  5. components/{auth,settings}/ folded into components-shared zone as legacy (PR-B2 cleanup)
  6. PortalLayout.vue uses explicit import { useRoute, useRouter } to allow vi.mock interception in tests

Open follow-ups for PR-B2

See session report in chat — auth-store merge, dual axios, full context-aware guards,
context-switcher, post-login landing, A13-3, A13-8, CookieBearerToken simplification,
DirectAdmin single-host config.

## Summary Single-SPA consolidation, stage B part 1: moves `apps/portal/` content into `apps/app/`, wires routes with explicit `meta.layout` + `meta.context`, deletes `apps/portal/` entirely. Auth refactor explicitly deferred to PR-B2. Refs: ARCH-CONSOLIDATION-2026-04.md §4.2 + §5 (WS-3), PR-A in main. ## What landed - 10 portal pages moved to `apps/app/src/pages/{portal,register}/` - 25 portal components moved to `apps/app/src/components/{shared/public-form,portal}/` - 6 byte-equivalent duplicate components deleted instead of moved - `packages/form-schema/` inlined under `apps/app/src/{types,utils,composables}/forms/` - Portal stores moved + auth store renamed to `usePortalAuthStore` (collision avoidance) - Single carve-out in `guards.ts`: `if (to.meta.context === 'portal') return` - ESLint boundaries sub-zones activated (TECH-WS3-BOUNDARIES-SUBZONES) - `apps/portal/` deleted entirely (-54k LOC, +5k LOC under `apps/app/`) - `register/success.vue` cleaned of auth-store dependency (relies on query param only) ## Verification - Lint: 0 errors, 0 warnings (beyond pre-existing boundaries v5→v6 deprecation warnings) - Typecheck: 0 errors - Frontend tests: 162 passed (up from 73) - Backend tests: 1486 passed (unchanged) - Paper smoke: portal-routes, register-routes, organizer-routes, platform-routes all resolve correctly ## What is explicitly NOT in this PR (→ PR-B2) - Auth-store merge (currently `useAuthStore` + `usePortalAuthStore` co-exist) - Dual-axios refactor - Route guards beyond `requiresAuth` and the meta-context carve-out - Context-switcher - Post-login landing logic - A13-3 open-redirect fix - A13-8 sessionStorage migration - `CookieBearerToken` backend simplification - DirectAdmin deploy config single-hostname ## Adaptations from the original prompt 1. No pnpm-workspace setup — Phase F skipped workspace.yaml + root scripts edits 2. `packages/form-schema/` was alias-only (no package.json); deletion via inline + drop alias 3. Route-guard carve-out via `meta.context === 'portal'` (1 if-block) 4. Register pages use `meta.public: true` (apps/app convention) + `meta.layout: 'PublicLayout'` 5. `components/{auth,settings}/` folded into `components-shared` zone as legacy (PR-B2 cleanup) 6. PortalLayout.vue uses explicit `import { useRoute, useRouter }` to allow vi.mock interception in tests ## Open follow-ups for PR-B2 See session report in chat — auth-store merge, dual axios, full context-aware guards, context-switcher, post-login landing, A13-3, A13-8, CookieBearerToken simplification, DirectAdmin single-host config.
bert.hausmans added 11 commits 2026-05-05 19:58:08 +02:00
Creates portal/register/shared/forms sub-folders ahead of the moves
in subsequent commits. Empty .gitkeep markers will be replaced by
real content as the moves land.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inlines the form-schema source folder (no package.json, alias-only)
into apps/app/src/composables/forms. Drops the @form-schema alias
from apps/app/vite.config.ts (replaced by @/composables/forms via
the existing @ alias). apps/portal vite + vitest configs keep
@form-schema as a temporary alias pointing at the new location so
portal tests/build keep working until apps/portal is removed at the
end of this PR. Two pure-logic form-schema tests moved alongside.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per WS-3 PR-B1 charter §4.2: portal pages relocate into the
single-SPA layout under apps/app/src/pages/portal/** (authenticated
portal context) and apps/app/src/pages/register/** (public
token-based form-fill / confirmation).

Updated meta blocks:
  - Portal pages: layout: 'PortalLayout', context: 'portal'
    (preserving original requiresAuth + nav fields)
  - Register pages: layout: 'PublicLayout' (drop requiresAuth)

Skipped (apps/portal duplicates of pages already in apps/app):
  index.vue, login.vue, wachtwoord-{vergeten,resetten}.vue,
  verify-email-change.vue. Deleted: [...path].vue (apps/app already
  has [...error].vue catch-all).

NOTE: Component/store/composable imports inside these files still
point at apps/portal-relative paths and will be rewritten in the
next commits. Build will not be green again until commit 6
(composables/lib).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- public-form/** (18 files + 7 component tests) → shared/public-form/**
  This is the runtime form-renderer; goes into shared/ because it will
  be reused by the organizer-app Form Builder preview (S3b).
- event/{Claimen,Informatie,Overzicht,Rooster}Tab.vue → portal/event/**
- portal/{StatusCard,EventCard,UserAvatarMenu}.vue → portal/** (no
  path change — both apps had a portal/ subfolder).
- AppLoadingIndicator.vue, auth/{PasswordRequirements,MfaChallengeCard}.vue,
  settings/Mfa{Disable,Email,Totp}SetupDialog.vue: portal copies
  deleted as duplicates of pre-existing apps/app components (diffs
  were trivial formatting only).

Inside the moved files: rewrote @form-schema/* → @/composables/forms/*
and @/components/{public-form,event/[Tab]} → new sub-zone paths.

Updated apps/app/tsconfig.json to drop the @form-schema path alias
and the packages/form-schema include path. Updated formSchema.ts to
import from @/composables/forms/types/formBuilder. Carried the
crypto polyfill from apps/portal/tests/setup.ts into
apps/app/tests/setup.ts (needed by useFormDraft tests landing in C.4).

NOTE: Some moved tests still fail because they reference portal
composables (usePublicFormSections, useFormDraft) that move in C.4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- apps/portal/src/stores/useAuthStore.ts →
  apps/app/src/stores/portal/usePortalAuthStore.ts. The export and
  defineStore id are renamed (useAuthStore → usePortalAuthStore,
  'auth' → 'portalAuth') so it can coexist with the organizer's
  apps/app/src/stores/useAuthStore. Lazy import inside
  resetPortalStoresSync() updated to the new path.
- apps/portal/src/stores/usePortalStore.ts →
  apps/app/src/stores/portal/usePortalStore.ts (no name change —
  apps/app does not have a usePortalStore).

All call sites in moved pages/components now import from
@/stores/portal/{usePortalStore,usePortalAuthStore} and call
usePortalAuthStore() instead of useAuthStore().

PR-B2 will merge this back into a single context-aware auth store.

Also includes the C.1 page meta-block updates (layout: 'PortalLayout'
| 'PublicLayout', context: 'portal') that were left unstaged after
the page-rename commit picked up only the path change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Composables (apps/portal/src/composables → apps/app/src/composables/):
  - useFormDraft, publicFormInjection → composables/ (root, used by
    shared/public-form components)
  - api/usePublicForm, api/usePublicFormSections,
    api/usePublicFormTimeSlots → composables/api/ (no collisions)
  - api/usePortalShifts, api/usePortalProfile, api/useVolunteerRegistration
    → composables/api/portal/ (subfolder per WS-3 PR-B1 charter to
    leave room for organizer-side namesakes without clashes)
  - api/useMfa → DELETED (apps/app version is a strict superset
    with extra invalidateQueries calls and the admin-reset mutation)

Types (apps/portal/src/types → apps/app/src/types/):
  - api, portal-shift, portal, registration → moved
  - mfa → DELETED (byte-identical to apps/app/src/types/mfa.ts)

Schemas:
  - apps/portal/src/schemas/registrationSchema.ts → apps/app/src/schemas/

Utils:
  - deviceFingerprint, paginationMeta → DELETED (byte-identical
    duplicates already in apps/app/src/utils/)

Lib:
  - apps/portal/src/lib/{axios,query-client}.ts → DELETED. apps/app's
    callback-bound axios (post-PR-A) and query-client are the
    canonical versions. Portal pages currently importing
    `@/lib/axios#apiClient` resolve to apps/app's apiClient with no
    behavioral change for cookie-based requests.

Tests: 4 composable specs (useFormDraft x2, usePublicFormSections,
usePublicFormTimeSlots) moved into __tests__/ subdirs alongside
their composables.

@form-schema imports inside the moved files rewritten to
@/composables/forms/*.

Vitest now: 23 files / 162 tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Migrates the navbar (event/platform two-mode toggle), mobile drawer
with avatar header + logout, RouterView Suspense wrapper, and footer
from apps/portal/src/layouts/portal.vue into the PortalLayout.vue
skeleton from PR-A. The skeleton's structure (VApp / VAppBar / VMain
/ VFooter) is preserved as the outer shell.

Notable adaptations:
  - useAuthStore → usePortalAuthStore (renamed in C.3)
  - usePortalStore import path → @/stores/portal/usePortalStore
  - mobile nav links now point at /portal/evenementen and /portal/profiel
    (the new sub-zone paths) instead of /evenementen and /profiel
  - explicit `import { useRoute, useRouter }` from vue-router so the
    vitest mock can intercept (auto-import not configured for these in
    the trimmed test config)

Updated PortalLayout.spec.ts to mock the two pinia stores plus
useSkins, vue-router, UserAvatarMenu, and AppLoadingIndicator. Tests
now assert the auth-conditional rendering: header + drawer hidden
when unauthenticated, main + footer always present.

Also pulls in the @form-schema → @/composables/forms/* import
rewrites in the C.4-moved composables that the previous commit's
rename-only diff left unstaged.

Vitest: 23 files / 162 tests, no errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Routing wiring (Phase D of WS-3 PR-B1):

- apps/app/src/plugins/1.router/guards.ts: add a single early-return
  carve-out before the org-selection redirect — `if (to.meta.context
  === 'portal') return`. Per ARCH-CONSOLIDATION-2026-04 §4.3,
  meta.context is the canonical contract; PR-B2 evolves the guards
  from this key to full context-aware logic (post-login landing,
  context-switcher, role checks).
- apps/app/env.d.ts: extend RouteMeta with the new layout names
  ('OrganizerLayout' | 'PortalLayout' | 'PublicLayout'), context,
  requiresAuth, requiresToken, navMode, navTitle.
- apps/app/typed-router.d.ts: regenerated by unplugin-vue-router to
  pick up portal/* and register/* route names.
- Page meta finalisation: portal pages have layout: 'PortalLayout',
  context: 'portal', preserving original requiresAuth + nav fields;
  register pages have layout: 'PublicLayout' + public: true (the
  apps/app guard convention for public routes, since meta.public is
  what the existing guard recognises).

Form-types restructure (boundaries cleanup):

- apps/app/src/composables/forms/types/formBuilder.ts → src/types/forms/
- apps/app/src/composables/forms/utils/{formValidation,validators}.ts
  → src/utils/forms/
- All `@/composables/forms/{types,utils}/*` imports rewritten across
  pages, components, composables, tests.
- This avoids a `types → composables` boundaries violation at
  src/types/formSchema.ts which re-exports primitives from the
  inlined form-schema. types/formSchema.ts now imports from
  @/types/forms/formBuilder which is in the same boundaries zone.

Lint cleanup for moved portal sources (apps/portal had no
.eslintrc.cjs; the migrated code now has to pass apps/app's stricter
config):

- axios.isAxiosError → named import { isAxiosError }
  (ClaimenTab, RoosterTab, profiel.vue)
- void schemaQuery.refetch() → schemaQuery.refetch()
  (register/[public_token].vue)
- if-then-else collapsed to single boolean return (formatFieldValue)
- :delay-on-touch-only="true" → delay-on-touch-only shorthand
  (FieldSectionPriority)
- ml-2 class → ms-2 (FieldAvailabilityPicker)
- multi-statement-per-line splits in profiel.vue + spec files
- unused emailConfigured ref removed (profiel.vue)
- one-component-per-file disabled with TODO TECH-WS3-PORTAL-LINT-CLEANUP
  ref (FieldOptionsLocale.spec.ts — multi-Wrapper test pattern)
- restored `import Draggable from 'vuedraggable'` after lint:fix
  removed it (template-only usage; the import IS needed)
- camelcase param renamed in FieldOptionsLocale harness factory
- typecheck nudge: spec state.data typed via PublicFormSectionOption[] /
  PublicFormTimeSlot[] aliases instead of Record<string, unknown>
- PortalLayout.vue: explicit `import { useRoute, useRouter }` so the
  vitest mock can intercept (the trimmed AutoImport set doesn't pull
  vue-router's auto-imports)

Vitest: 23 / 162 passing. Lint: 0 errors / 0 new warnings (only the
pre-existing boundaries v5→v6 deprecation warnings remain). Typecheck:
clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the WS-3 §4.2 sub-zone classification to the apps/app
boundaries matrix:

- components-{shared,portal,organizer} alongside the legacy
  components type. components/{auth,settings} are folded into
  components-shared as the legacy cross-context home for MFA dialogs
  + PasswordRequirements (used by both organizer reset-password and
  portal wachtwoord-instellen / profiel).
- composables-forms (src/composables/forms/**) — pure form-runtime
  helpers reusable from organizer Form Builder later.
- stores-portal (src/stores/portal/**) — keeps the portal auth +
  portal store walled off from the organizer auth surface.
- pages-{register,portal,platform,organizer} alongside the legacy
  pages type — register pages cannot reach into stores or
  components-portal/-organizer; portal pages cannot reach
  components-organizer; organizer + platform pages cannot reach
  stores-portal or components-portal.

Cross-context edges are forbidden (organizer ↛ portal,
shared ↛ portal/organizer). Two pragmatic exceptions are documented
inline:
  - components-shared accepts the legacy auth/ + settings/ paths
    until PR-B2 cleanup re-homes them under shared/{auth,settings}/.
  - pages-register may read stores-portal because success.vue
    optionally enriches with the portal user when authenticated.
    PR-B2 may move success.vue into pages-portal so this drops.

Lint: 0 errors / 0 new warnings (only the pre-existing
boundaries v5→v6 deprecation warnings, which apply to all 19 rules
now). Tests: 23 / 162 pass. Typecheck clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Completes WS-3 PR-B1 charter §4.2: portal is fully consumed by
apps/app under /portal/** (authenticated portal routes) and
/register/** (public token-based form-fill). All portal source has
moved or been merged in earlier commits in this PR.

Adaptations from the original prompt's Phase F:
  - pnpm-workspace.yaml does not exist at the repo root (the monorepo
    isn't a pnpm workspace; each app has its own package.json /
    node_modules / scripts). No edit needed.
  - Root package.json has no `dev:portal` / `build:portal` scripts.
    No cleanup needed.
  - Skipped `pnpm -w build` — apps/app builds via its own scripts.

Deletes 384 portal files (build configs, layouts, plugins, vendored
@layouts, public/, dev/prod Dockerfiles, nginx.conf, env.d.ts,
themeConfig, tsconfig, package.json, lockfile, etc.). All authentic
portal logic is preserved in apps/app/src — verified by:
  - Vitest 23 / 162 passing
  - vue-tsc --noEmit clean
  - eslint clean (zero new errors / warnings)

NOT verified at this point: `pnpm build`. The build fails on a
pre-existing missing `flatpickr` stylesheet import in
src/@core/components/app-form-elements/AppDateTimePicker.vue —
present on main pre-PR, unrelated to this work, and tracked
separately. Reproduced on plain `main` without any of these changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bert.hausmans merged commit 06b3a637b2 into main 2026-05-05 20:21:05 +02:00
bert.hausmans deleted branch feat/ws-3-pr-b1-portal-moves 2026-05-05 20:21:05 +02:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bert.hausmans/crewli#4