The frontend component of PuffPastry, meant to interface with the backend, which in turn interfaces with the smart contract.
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 
 
 

10 KiB

sessionId
session-261002-154517-19l9

Requirements

Overview & Goals

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

Scope

  • In Scope:
    • Transitioning router history from createMemoryHistory() to createWebHistory().
    • Configuring nested routes under HomeView for the empty state (/) and specific proposals (/proposals/:id).
    • Replacing the static conditional component rendering in HomeView.vue with <RouterView />.
    • Creating a dedicated route view (ProposalView.vue) to fetch and render proposal details based on route parameters.
    • Updating Sidebar.vue and ProposalContainer.vue to trigger router navigation and synchronize active item highlights with the active route.
    • Ensuring smooth mobile UX with automatic drawer dismissal upon navigation.
  • Out of Scope:
    • Changes to backend APIs or data models.
    • Authentication guard modifications.
    • Modifications to proposal editing or amendment creation workflows beyond preserving their existing modal behavior.

User Stories

  • 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.
  • As a user, I want browser back and forward buttons to navigate between previously viewed proposals without page reloads.
  • As a user, I want refreshing the browser on a proposal page to reload the exact same proposal seamlessly.

Functional Requirements

  • Navigating to / displays the proposal sidebar alongside ProposalEmptyState in the main view area.
  • Navigating to /proposals/:id loads and displays the details for the proposal with ID :id.
  • 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.
  • Invalid or non-existent proposal IDs show an appropriate empty or not-found state without breaking the application shell.
  • Direct URL access to /proposals/:id highlights the respective item in the sidebar once the proposal list is loaded.

Non-Functional Requirements

  • Performance: Avoid unnecessary re-fetching of the full proposal list when switching between proposal detail routes.
  • Maintainability: Clear separation between route-level container components and presentational components (ProposalDetail.vue, ProposalEmptyState.vue).
  • User Experience: Instant visual feedback using existing loading indicators during route transitions.

Technical Design

Current Implementation

  • src/router/index.ts uses createMemoryHistory() and only registers a single route { path: '/', component: HomeView }.
  • 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.
  • src/components/Sidebar.vue tracks selection via an internal selectedId ref and emits an event upward.
  • src/App.vue renders the top-level <RouterView />, which mounts HomeView.vue.

Key Decisions

  • Nested Routing in HomeView.vue:
    • Decision: Nest child routes inside HomeView rather than creating separate top-level pages.
    • 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.
  • History Mode:
    • Decision: Switch from createMemoryHistory() to createWebHistory().
    • Rationale: Enables standard URL paths (/proposals/:id) that work with browser history, bookmarks, and link sharing.
  • Route Component Structure:
    • 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.
    • Rationale: Keeps ProposalDetail.vue purely presentational and reusable while decoupling route logic and lifecycle hooks.
  • Active State Synchronization:
    • Decision: Derive sidebar active state from useRoute().params.id instead of disconnected local component state.
    • Rationale: Guarantees consistency across direct URL hits, back/forward browser navigation, and clicks.

Architecture Diagram

graph TD
  App[App.vue - Root RouterView] --> HomeView[HomeView.vue]
  HomeView --> Sidebar[Sidebar.vue - List & Filters]
  HomeView --> ContentRouterView[Nested RouterView]
  ContentRouterView -- path: '/' --> EmptyState[ProposalEmptyState.vue]
  ContentRouterView -- path: '/proposals/:id' --> ProposalView[ProposalView.vue]
  ProposalView --> ProposalDetail[ProposalDetail.vue]
  Sidebar -.->|router.push('/proposals/:id')| ProposalView

Proposed Changes

  1. src/router/index.ts:
    • Change history mode to createWebHistory().
    • Update routes:
      {
        path: '/',
        component: HomeView,
        children: [
          { path: '', name: 'home', component: ProposalEmptyState },
          { path: 'proposals/:id', name: 'proposal', component: () => import('../views/ProposalView.vue'), props: true }
        ]
      }
      
  2. src/views/HomeView.vue:
    • Replace <ProposalDetail>, <ProposalEmptyState>, and content <LoadingSpinner> in <main> with <RouterView />.
    • Remove manual selected state and handleSelection logic since routing handles proposal loading.
    • Listen to route changes to auto-close isSidebarOpen on mobile viewports.
  3. src/views/ProposalView.vue (New File):
    • Accept id prop (coerced to number).
    • Watch id to invoke ProposalService.getProposal(id) with useLoading('content').
    • Render ProposalDetail when loaded, or appropriate error/empty fallback on failure.
  4. src/components/Sidebar.vue & src/components/ProposalContainer.vue:
    • Update handleSelection to navigate using router.push({ name: 'proposal', params: { id: proposal.id } }).
    • Determine selected state dynamically from the active route parameter matching proposal.id.

File Structure

  • src/router/index.ts (modified) — Configure HTML5 web history and nested routes.
  • src/views/HomeView.vue (modified) — Host nested <RouterView /> and layout shell.
  • src/views/ProposalView.vue (new) — Route view component for fetching and displaying a proposal.
  • src/components/Sidebar.vue (modified) — Derive active selection from route and dispatch route navigation.

Risks & Mitigations

  • Route Param Type Coercion: Route params are strings by default; parsing Number(route.params.id) ensures compatibility with ProposalService.getProposal(number).
  • Rapid Navigation Race Conditions: Watching the proposal ID and ensuring the latest request updates state avoids stale content overwrites.

Testing

Validation Approach

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.

Key Scenarios

  1. Direct Deep Link Loading:
    • Navigating directly to /proposals/1 loads proposal #1 details and highlights item #1 in the sidebar once the list is loaded.
  2. Sidebar Item Click Navigation:
    • Clicking a proposal in the sidebar navigates to /proposals/:id, updates the address bar URL, and displays the corresponding proposal content.
  3. Browser History Navigation:
    • Clicking back and forward buttons in the browser switches between viewed proposals and keeps sidebar selection synchronized.
  4. Empty State on Root Route:
    • Navigating to / renders ProposalEmptyState in the main content pane without errors.
  5. Mobile Drawer Behavior:
    • Selecting a proposal on mobile screens collapses the sidebar overlay and displays the selected proposal.

Edge Cases

  • Invalid / Non-Numeric Proposal ID: Navigating to /proposals/abc or /proposals/-1 handles the error gracefully without throwing uncaught exceptions.
  • Non-Existent Proposal ID (404): Displays a user-friendly error message or empty state when the API returns an error for a missing proposal.
  • Rapid Sequential Clicks: Quickly clicking multiple sidebar items displays only the data for the final selected proposal.

Delivery Steps

✓ Step 1: Configure Vue Router with Web History and Nested Routes

Vue Router is configured with web history and nested routes for proposal viewing.

  • Replace createMemoryHistory() with createWebHistory() in src/router/index.ts to support browser URLs, bookmarks, and standard history navigation.
  • 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.
  • Enable props: true (or custom prop mapping) on the dynamic route to pass the proposal id cleanly as a typed number prop.

✓ Step 2: Implement Proposal Route View and Data Fetching

Dedicated route view created to load and display individual proposals based on URL parameters.

  • Create src/views/ProposalView.vue (or ProposalDetailView.vue) to encapsulate data retrieval for the selected proposal ID.
  • Integrate ProposalService.getProposal(id) with reactive route parameter watchers so switching proposals updates content without remounting overhead.
  • Coordinate loading states with useLoading('content') and render ProposalDetail or handle not-found/error states gracefully.

✓ Step 3: Update HomeView and Sidebar for Route-Driven Navigation

HomeView and Sidebar leverage Vue Router navigation and automatic route-based active selection.

  • Update src/views/HomeView.vue main area to render <RouterView /> instead of manual conditional rendering.
  • Wire src/components/Sidebar.vue and src/components/ProposalContainer.vue to navigate to /proposals/:id upon proposal click.
  • 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.
  • Automatically dismiss the mobile sidebar overlay upon route navigation on small viewports.