WS-3 PR-B1: Portal moves + routing wiring #4
Reference in New Issue
Block a user
Delete Branch "feat/ws-3-pr-b1-portal-moves"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Single-SPA consolidation, stage B part 1: moves
apps/portal/content intoapps/app/, wires routes with explicitmeta.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
apps/app/src/pages/{portal,register}/apps/app/src/components/{shared/public-form,portal}/packages/form-schema/inlined underapps/app/src/{types,utils,composables}/forms/usePortalAuthStore(collision avoidance)guards.ts:if (to.meta.context === 'portal') returnapps/portal/deleted entirely (-54k LOC, +5k LOC underapps/app/)register/success.vuecleaned of auth-store dependency (relies on query param only)Verification
What is explicitly NOT in this PR (→ PR-B2)
useAuthStore+usePortalAuthStoreco-exist)requiresAuthand the meta-context carve-outCookieBearerTokenbackend simplificationAdaptations from the original prompt
packages/form-schema/was alias-only (no package.json); deletion via inline + drop aliasmeta.context === 'portal'(1 if-block)meta.public: true(apps/app convention) +meta.layout: 'PublicLayout'components/{auth,settings}/folded intocomponents-sharedzone as legacy (PR-B2 cleanup)import { useRoute, useRouter }to allow vi.mock interception in testsOpen 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.
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>