The frontend component of PuffPastry, meant to interface with the backend, which in turn interfaces with the smart contract.
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

linkable-proposals-router-view.md 10 KiB

​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. ---
  2. sessionId: session-261002-154517-19l9
  3. ---
  4. # Requirements
  5. ### Overview & Goals
  6. Enhance user utility and application navigation by making individual proposals linkable and bookmarkable via browser URLs. Replacing in-memory state selection with URL-driven routing maximizes efficiency, enables direct sharing among users, and preserves standard browser history navigation (back/forward).
  7. ### Scope
  8. - **In Scope:**
  9. - Transitioning router history from `createMemoryHistory()` to `createWebHistory()`.
  10. - Configuring nested routes under `HomeView` for the empty state (`/`) and specific proposals (`/proposals/:id`).
  11. - Replacing the static conditional component rendering in `HomeView.vue` with `<RouterView />`.
  12. - Creating a dedicated route view (`ProposalView.vue`) to fetch and render proposal details based on route parameters.
  13. - Updating `Sidebar.vue` and `ProposalContainer.vue` to trigger router navigation and synchronize active item highlights with the active route.
  14. - Ensuring smooth mobile UX with automatic drawer dismissal upon navigation.
  15. - **Out of Scope:**
  16. - Changes to backend APIs or data models.
  17. - Authentication guard modifications.
  18. - Modifications to proposal editing or amendment creation workflows beyond preserving their existing modal behavior.
  19. ### User Stories
  20. - **As a user**, I want to share a direct URL to a specific proposal so that collaborators can immediately view the proposal details without manually finding it in the sidebar.
  21. - **As a user**, I want browser back and forward buttons to navigate between previously viewed proposals without page reloads.
  22. - **As a user**, I want refreshing the browser on a proposal page to reload the exact same proposal seamlessly.
  23. ### Functional Requirements
  24. - Navigating to `/` displays the proposal sidebar alongside `ProposalEmptyState` in the main view area.
  25. - Navigating to `/proposals/:id` loads and displays the details for the proposal with ID `:id`.
  26. - Clicking a proposal in `Sidebar.vue` navigates to `/proposals/:id`, highlights the selected proposal in the sidebar, and closes the mobile sidebar drawer if open.
  27. - Invalid or non-existent proposal IDs show an appropriate empty or not-found state without breaking the application shell.
  28. - Direct URL access to `/proposals/:id` highlights the respective item in the sidebar once the proposal list is loaded.
  29. ### Non-Functional Requirements
  30. - **Performance:** Avoid unnecessary re-fetching of the full proposal list when switching between proposal detail routes.
  31. - **Maintainability:** Clear separation between route-level container components and presentational components (`ProposalDetail.vue`, `ProposalEmptyState.vue`).
  32. - **User Experience:** Instant visual feedback using existing loading indicators during route transitions.
  33. # Technical Design
  34. ### Current Implementation
  35. - `src/router/index.ts` uses `createMemoryHistory()` and only registers a single route `{ path: '/', component: HomeView }`.
  36. - `src/views/HomeView.vue` manages the `selected` proposal state locally in component memory (`ref<ProposalWithParagraphs>()`). Clicking a sidebar item triggers `handleSelection` which directly calls `ProposalService.getProposal(id)` and sets local state.
  37. - `src/components/Sidebar.vue` tracks selection via an internal `selectedId` ref and emits an event upward.
  38. - `src/App.vue` renders the top-level `<RouterView />`, which mounts `HomeView.vue`.
  39. ### Key Decisions
  40. - **Nested Routing in `HomeView.vue`:**
  41. - *Decision:* Nest child routes inside `HomeView` rather than creating separate top-level pages.
  42. - *Rationale:* Preserves the persistent sidebar layout and list state across navigation without re-fetching proposals on every route change, maximizing rendering efficiency and minimizing layout shifts.
  43. - **History Mode:**
  44. - *Decision:* Switch from `createMemoryHistory()` to `createWebHistory()`.
  45. - *Rationale:* Enables standard URL paths (`/proposals/:id`) that work with browser history, bookmarks, and link sharing.
  46. - **Route Component Structure:**
  47. - *Decision:* Introduce `src/views/ProposalView.vue` as the route-level child component that reads route params, fetches data via `ProposalService`, and passes data to the existing `ProposalDetail.vue`.
  48. - *Rationale:* Keeps `ProposalDetail.vue` purely presentational and reusable while decoupling route logic and lifecycle hooks.
  49. - **Active State Synchronization:**
  50. - *Decision:* Derive sidebar active state from `useRoute().params.id` instead of disconnected local component state.
  51. - *Rationale:* Guarantees consistency across direct URL hits, back/forward browser navigation, and clicks.
  52. ### Architecture Diagram
  53. ```mermaid
  54. graph TD
  55. App[App.vue - Root RouterView] --> HomeView[HomeView.vue]
  56. HomeView --> Sidebar[Sidebar.vue - List & Filters]
  57. HomeView --> ContentRouterView[Nested RouterView]
  58. ContentRouterView -- path: '/' --> EmptyState[ProposalEmptyState.vue]
  59. ContentRouterView -- path: '/proposals/:id' --> ProposalView[ProposalView.vue]
  60. ProposalView --> ProposalDetail[ProposalDetail.vue]
  61. Sidebar -.->|router.push('/proposals/:id')| ProposalView
  62. ```
  63. ### Proposed Changes
  64. 1. **`src/router/index.ts`:**
  65. - Change history mode to `createWebHistory()`.
  66. - Update routes:
  67. ```ts
  68. {
  69. path: '/',
  70. component: HomeView,
  71. children: [
  72. { path: '', name: 'home', component: ProposalEmptyState },
  73. { path: 'proposals/:id', name: 'proposal', component: () => import('../views/ProposalView.vue'), props: true }
  74. ]
  75. }
  76. ```
  77. 2. **`src/views/HomeView.vue`:**
  78. - Replace `<ProposalDetail>`, `<ProposalEmptyState>`, and content `<LoadingSpinner>` in `<main>` with `<RouterView />`.
  79. - Remove manual `selected` state and `handleSelection` logic since routing handles proposal loading.
  80. - Listen to route changes to auto-close `isSidebarOpen` on mobile viewports.
  81. 3. **`src/views/ProposalView.vue` (New File):**
  82. - Accept `id` prop (coerced to number).
  83. - Watch `id` to invoke `ProposalService.getProposal(id)` with `useLoading('content')`.
  84. - Render `ProposalDetail` when loaded, or appropriate error/empty fallback on failure.
  85. 4. **`src/components/Sidebar.vue` & `src/components/ProposalContainer.vue`:**
  86. - Update `handleSelection` to navigate using `router.push({ name: 'proposal', params: { id: proposal.id } })`.
  87. - Determine `selected` state dynamically from the active route parameter matching `proposal.id`.
  88. ### File Structure
  89. - `src/router/index.ts` *(modified)* — Configure HTML5 web history and nested routes.
  90. - `src/views/HomeView.vue` *(modified)* — Host nested `<RouterView />` and layout shell.
  91. - `src/views/ProposalView.vue` *(new)* — Route view component for fetching and displaying a proposal.
  92. - `src/components/Sidebar.vue` *(modified)* — Derive active selection from route and dispatch route navigation.
  93. ### Risks & Mitigations
  94. - **Route Param Type Coercion:** Route params are strings by default; parsing `Number(route.params.id)` ensures compatibility with `ProposalService.getProposal(number)`.
  95. - **Rapid Navigation Race Conditions:** Watching the proposal ID and ensuring the latest request updates state avoids stale content overwrites.
  96. # Testing
  97. ### Validation Approach
  98. Verification focuses on confirming that URLs accurately reflect the displayed proposal, deep links load expected data, and navigation between proposals works smoothly via both UI clicks and browser history.
  99. ### Key Scenarios
  100. 1. **Direct Deep Link Loading:**
  101. - Navigating directly to `/proposals/1` loads proposal #1 details and highlights item #1 in the sidebar once the list is loaded.
  102. 2. **Sidebar Item Click Navigation:**
  103. - Clicking a proposal in the sidebar navigates to `/proposals/:id`, updates the address bar URL, and displays the corresponding proposal content.
  104. 3. **Browser History Navigation:**
  105. - Clicking back and forward buttons in the browser switches between viewed proposals and keeps sidebar selection synchronized.
  106. 4. **Empty State on Root Route:**
  107. - Navigating to `/` renders `ProposalEmptyState` in the main content pane without errors.
  108. 5. **Mobile Drawer Behavior:**
  109. - Selecting a proposal on mobile screens collapses the sidebar overlay and displays the selected proposal.
  110. ### Edge Cases
  111. - **Invalid / Non-Numeric Proposal ID:** Navigating to `/proposals/abc` or `/proposals/-1` handles the error gracefully without throwing uncaught exceptions.
  112. - **Non-Existent Proposal ID (404):** Displays a user-friendly error message or empty state when the API returns an error for a missing proposal.
  113. - **Rapid Sequential Clicks:** Quickly clicking multiple sidebar items displays only the data for the final selected proposal.
  114. # Delivery Steps
  115. ### ✓ Step 1: Configure Vue Router with Web History and Nested Routes
  116. Vue Router is configured with web history and nested routes for proposal viewing.
  117. - Replace `createMemoryHistory()` with `createWebHistory()` in `src/router/index.ts` to support browser URLs, bookmarks, and standard history navigation.
  118. - Define child routes under `HomeView`: an empty child path `''` mapped to `ProposalEmptyState.vue` and a dynamic child path `'proposals/:id'` mapped to a proposal detail route view.
  119. - Enable `props: true` (or custom prop mapping) on the dynamic route to pass the proposal `id` cleanly as a typed number prop.
  120. ### ✓ Step 2: Implement Proposal Route View and Data Fetching
  121. Dedicated route view created to load and display individual proposals based on URL parameters.
  122. - Create `src/views/ProposalView.vue` (or `ProposalDetailView.vue`) to encapsulate data retrieval for the selected proposal ID.
  123. - Integrate `ProposalService.getProposal(id)` with reactive route parameter watchers so switching proposals updates content without remounting overhead.
  124. - Coordinate loading states with `useLoading('content')` and render `ProposalDetail` or handle not-found/error states gracefully.
  125. ### ✓ Step 3: Update HomeView and Sidebar for Route-Driven Navigation
  126. HomeView and Sidebar leverage Vue Router navigation and automatic route-based active selection.
  127. - Update `src/views/HomeView.vue` main area to render `<RouterView />` instead of manual conditional rendering.
  128. - Wire `src/components/Sidebar.vue` and `src/components/ProposalContainer.vue` to navigate to `/proposals/:id` upon proposal click.
  129. - Sync active proposal highlighting in `Sidebar.vue` with current `route.params.id` to ensure correct state on direct URL loads, back/forward navigation, and clicks.
  130. - Automatically dismiss the mobile sidebar overlay upon route navigation on small viewports.