feat(ux): welcome banner, dashboard auth fix, docs update
- WelcomeBanner.tsx: guest-only dismissible onboarding card on dashboard (localStorage pv_seen_welcome, Browse Bills CTA, X dismiss) - useDashboard: add !!token to query key so login/logout triggers a fresh fetch without manual refresh - ARCHITECTURE.md: WelcomeBanner component, auth-aware query keys, v0.6.1 feature history entry - Roadmap: mark welcome banner items complete - Add MVP planning notes (Phase 3-6 roadmap draft) Co-Authored-By: Jack Levy
This commit is contained in:
@@ -764,6 +764,9 @@ Compact bill preview showing bill ID, title, sponsor with party badge, latest ac
|
|||||||
**`TrendChart.tsx`**
|
**`TrendChart.tsx`**
|
||||||
Line chart of `composite_score` over time with tooltip breakdown of each data source.
|
Line chart of `composite_score` over time with tooltip breakdown of each data source.
|
||||||
|
|
||||||
|
**`WelcomeBanner.tsx`**
|
||||||
|
Dismissible onboarding card rendered at the top of the dashboard. Shown only to guests (no JWT token). On dismiss — via the × button, the "Dismiss" link, or the "Browse Bills" CTA — sets `localStorage["pv_seen_welcome"] = "1"` and hides permanently. Reads localStorage after mount to avoid hydration mismatch; renders nothing until client-side state is resolved.
|
||||||
|
|
||||||
### Utility Functions (`lib/utils.ts`)
|
### Utility Functions (`lib/utils.ts`)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
@@ -794,6 +797,8 @@ interface AuthState {
|
|||||||
// Persisted to localStorage as "pocketveto-auth"
|
// Persisted to localStorage as "pocketveto-auth"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Auth-aware query keys:** TanStack Query keys that return different data for guests vs authenticated users include `!!token` in their key (e.g. `["dashboard", !!token]`). This ensures a fresh fetch fires automatically on login or logout without manual cache invalidation.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Authentication
|
## Authentication
|
||||||
@@ -993,6 +998,18 @@ Nginx uses `resolver 127.0.0.11 valid=10s` (Docker's internal DNS) so upstream c
|
|||||||
- Manual Controls split into two sections: always-visible recurring controls (Poll, Members, Trends, Actions, Resume) and a collapsible **Maintenance** section for one-time backfill tasks
|
- Manual Controls split into two sections: always-visible recurring controls (Poll, Members, Trends, Actions, Resume) and a collapsible **Maintenance** section for one-time backfill tasks
|
||||||
- Maintenance section header shows "⚠ action needed" when any backfill has a non-zero count
|
- Maintenance section header shows "⚠ action needed" when any backfill has a non-zero count
|
||||||
|
|
||||||
|
### v0.6.1 — Welcome Banner & Dashboard Auth Fix
|
||||||
|
|
||||||
|
**Welcome Banner:**
|
||||||
|
- `WelcomeBanner.tsx` — dismissible onboarding card shown only to guests at the top of the dashboard
|
||||||
|
- Three bullet points: follow bills/members/topics, see what changed, Back to Source citations
|
||||||
|
- "Browse Bills" CTA navigates to `/bills` and dismisses; × and "Dismiss" button also dismiss
|
||||||
|
- Dismissed state stored in `localStorage["pv_seen_welcome"]`; never shown to logged-in users
|
||||||
|
|
||||||
|
**Dashboard Auth-Aware Query Key:**
|
||||||
|
- `useDashboard` hook query key changed from `["dashboard"]` to `["dashboard", !!token]`
|
||||||
|
- Fixes stale cache issue where logging in showed the guest feed until a manual refresh
|
||||||
|
|
||||||
### v0.2.2 — Sponsor Linking & Search Fixes
|
### v0.2.2 — Sponsor Linking & Search Fixes
|
||||||
- **Root cause fixed:** Congress.gov list API does not return sponsor data — only the detail endpoint does. Poller now calls the detail endpoint for each new bill to get the sponsor and populate `bill.sponsor_id`
|
- **Root cause fixed:** Congress.gov list API does not return sponsor data — only the detail endpoint does. Poller now calls the detail endpoint for each new bill to get the sponsor and populate `bill.sponsor_id`
|
||||||
- **Backfill task:** `backfill_sponsor_ids` Celery task + `/api/admin/backfill-sponsors` endpoint + "Backfill Sponsors" button in Admin UI — fixes existing bills with `NULL` sponsor_id (~10 req/sec, ~3 min for 1,600 bills)
|
- **Backfill task:** `backfill_sponsor_ids` Celery task + `/api/admin/backfill-sponsors` endpoint + "Backfill Sponsors" button in Admin UI — fixes existing bills with `NULL` sponsor_id (~10 req/sec, ~3 min for 1,600 bills)
|
||||||
|
|||||||
93
MVP threshold this make v1 complete.md
Normal file
93
MVP threshold this make v1 complete.md
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
|
||||||
|
### Phase 3 — Personal Workflow & Context
|
||||||
|
|
||||||
|
_Focus: Allowing users to organize data and adding the "Cross-Bill" intelligence layer._
|
||||||
|
|
||||||
|
- **Collections / Watchlists:** `collections` table for public/private bill groupings shareable via UUID tokens.
|
||||||
|
|
||||||
|
- **Personal Notes:** Private `bill_notes` table to store user stances, tags, and pinned insights on bill detail pages.
|
||||||
|
|
||||||
|
|
||||||
|
- **Cross-Bill Referencing (New):**
|
||||||
|
|
||||||
|
- **Extraction:** Update LLM prompts to identify and extract related bill identifiers (e.g., "H.R. 1234") from text.
|
||||||
|
|
||||||
|
- **Referential Table:** Implement `bill_references` to map source-to-target bill relationships with context quotes.
|
||||||
|
|
||||||
|
- **Ghost Hydration:** Create the `/api/admin/bills/hydrate` endpoint to fetch and analyze referenced bills not currently in the DB.
|
||||||
|
|
||||||
|
- **UI Hover Previews:** Use `Radix UI` popovers in the `BriefPanel` to show snapshots of referenced legislation without leaving the page.
|
||||||
|
|
||||||
|
- **Weekly Digest:** Celery beat task to dispatch 7-day summaries of followed bills via ntfy and RSS.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 4 — Accountability & Member Scoring
|
||||||
|
|
||||||
|
_Focus: Deep-diving into legislative performance and voting records._
|
||||||
|
|
||||||
|
- **Votes & Committees:** Fetch roll-call votes and committee actions from Congress.gov into a new `bill_votes` table.
|
||||||
|
|
||||||
|
- **Member Effectiveness Score:** Nightly Celery task calculating scores based on sponsored bills, advancement milestones, and "bills enacted" metrics.
|
||||||
|
|
||||||
|
- **Bipartisan Progress Metric (Proposed):** Enhance effectiveness scores by weighting co-sponsorship across party lines and committee movement in opposing chambers.
|
||||||
|
|
||||||
|
- **Representation Alignment:** Neutral view showing how member votes align with a user’s followed topics.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 5 — Systemic Intelligence
|
||||||
|
|
||||||
|
_Focus: Enhancing search and providing broader legislative context._
|
||||||
|
|
||||||
|
- **Search Improvements:** Add filters for bill type, status, chamber, and date; implement "Search within member bills".
|
||||||
|
|
||||||
|
- **Topic-Scoped Search:** Allow users to search legislation exclusively within followed topics like "Healthcare" or "Energy".
|
||||||
|
|
||||||
|
- **Member Effectiveness Transparency:** Display the "Member Effectiveness" formula on profile pages to maintain non-partisan trust.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 6 — Desktop & Polish
|
||||||
|
|
||||||
|
_Focus: Optimization for professional-grade power users._
|
||||||
|
|
||||||
|
- **Desktop View:** Multi-column layout with sticky sidebars and expanded grids optimized for large screens.
|
||||||
|
|
||||||
|
- **Notification Channels v2:** Expand beyond ntfy/RSS to include Discord webhooks, Telegram, and SMTP email.
|
||||||
|
|
||||||
|
- **Backfill Tasks:** Automate `first_name` and `last_name` population for all member records.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Updated Technical Implementation Details
|
||||||
|
|
||||||
|
### Database Schema Updates
|
||||||
|
|
||||||
|
|**Table**|**New Columns / Purpose**|
|
||||||
|
|---|---|
|
||||||
|
|**`bill_references`**|`source_id`, `target_id`, `context_quote` to link related bills.|
|
||||||
|
|**`bill_notes`**|`user_id`, `bill_id`, `content`, `stance`, `tags`, `pinned`.|
|
||||||
|
|**`collections`**|`user_id`, `name`, `slug`, `is_public` for shared watchlists.|
|
||||||
|
|**`bill_votes`**|Stores roll-call data and results for accountability tracking.|
|
||||||
|
|
||||||
|
### Enhanced LLM Pipeline
|
||||||
|
|
||||||
|
The `llm_processor.py` task will now perform a dual-pass:
|
||||||
|
|
||||||
|
1. **Analysis:** Generate the standard brief with citations and fact/inference labels.
|
||||||
|
|
||||||
|
2. **Mapping:** Extract bill IDs found in the text and insert them into the `bill_references` table. If a target ID is missing from the `bills` table, it triggers a low-priority `hydrate_bill` task.
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Guardrails
|
||||||
|
|
||||||
|
- **Selective Hydration:** The poller will only "Ghost Hydrate" referenced bills if they are from the current or previous Congress to prevent API exhaustion.
|
||||||
|
|
||||||
|
- **Cached Previews:** Referenced bill snapshots for hover tooltips will be cached in Redis to minimize database hits during dashboard scrolling.
|
||||||
|
|
||||||
|
|
||||||
@@ -206,9 +206,9 @@ Don’t store it server-side unless you already have user accounts and it’s pa
|
|||||||
|
|
||||||
### Backlog item (checkboxes)
|
### Backlog item (checkboxes)
|
||||||
|
|
||||||
* [ ] First-visit welcome UI (banner/card + optional toast)
|
* [x] First-visit welcome UI (banner/card — guests only, shown on dashboard)
|
||||||
* [ ] Dismiss + “don’t show again” (localStorage)
|
* [x] Dismiss + “don’t show again” (localStorage `pv_seen_welcome`)
|
||||||
* [ ] CTA: Add first follow
|
* [x] CTA: Browse Bills
|
||||||
* [ ] CTA: Load demo data (optional)
|
* [ ] CTA: Load demo data (optional)
|
||||||
* [ ] Link: “How it works” page/modal (optional)
|
* [ ] Link: “How it works” page/modal (optional)
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { TrendingUp, BookOpen, Flame, RefreshCw } from "lucide-react";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useDashboard } from "@/lib/hooks/useDashboard";
|
import { useDashboard } from "@/lib/hooks/useDashboard";
|
||||||
import { BillCard } from "@/components/shared/BillCard";
|
import { BillCard } from "@/components/shared/BillCard";
|
||||||
|
import { WelcomeBanner } from "@/components/shared/WelcomeBanner";
|
||||||
import { adminAPI } from "@/lib/api";
|
import { adminAPI } from "@/lib/api";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useAuthStore } from "@/stores/authStore";
|
import { useAuthStore } from "@/stores/authStore";
|
||||||
@@ -40,6 +41,8 @@ export default function DashboardPage() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<WelcomeBanner />
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="text-center py-20 text-muted-foreground">Loading dashboard...</div>
|
<div className="text-center py-20 text-muted-foreground">Loading dashboard...</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
71
frontend/components/shared/WelcomeBanner.tsx
Normal file
71
frontend/components/shared/WelcomeBanner.tsx
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { X, BookOpen, GitCompare, ShieldCheck } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useAuthStore } from "@/stores/authStore";
|
||||||
|
|
||||||
|
const STORAGE_KEY = "pv_seen_welcome";
|
||||||
|
|
||||||
|
export function WelcomeBanner() {
|
||||||
|
const token = useAuthStore((s) => s.token);
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!token && localStorage.getItem(STORAGE_KEY) !== "1") {
|
||||||
|
setVisible(true);
|
||||||
|
}
|
||||||
|
}, [token]);
|
||||||
|
|
||||||
|
const dismiss = () => {
|
||||||
|
localStorage.setItem(STORAGE_KEY, "1");
|
||||||
|
setVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!visible) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative bg-card border border-border rounded-lg p-5 pr-10">
|
||||||
|
<button
|
||||||
|
onClick={dismiss}
|
||||||
|
title="Dismiss"
|
||||||
|
className="absolute top-3 right-3 p-1 rounded text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
|
||||||
|
>
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<h2 className="font-semibold text-base mb-3">Welcome to PocketVeto</h2>
|
||||||
|
|
||||||
|
<ul className="space-y-2 mb-4">
|
||||||
|
<li className="flex items-start gap-2.5 text-sm text-muted-foreground">
|
||||||
|
<BookOpen className="w-4 h-4 mt-0.5 shrink-0 text-primary" />
|
||||||
|
Follow bills, members, or topics — get low-noise alerts when things actually move
|
||||||
|
</li>
|
||||||
|
<li className="flex items-start gap-2.5 text-sm text-muted-foreground">
|
||||||
|
<GitCompare className="w-4 h-4 mt-0.5 shrink-0 text-primary" />
|
||||||
|
See <em>what changed</em> in plain English when bills are amended
|
||||||
|
</li>
|
||||||
|
<li className="flex items-start gap-2.5 text-sm text-muted-foreground">
|
||||||
|
<ShieldCheck className="w-4 h-4 mt-0.5 shrink-0 text-primary" />
|
||||||
|
Verify every AI claim with <strong>Back to Source</strong> citations from the bill text
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Link
|
||||||
|
href="/bills"
|
||||||
|
onClick={dismiss}
|
||||||
|
className="px-3 py-1.5 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors"
|
||||||
|
>
|
||||||
|
Browse Bills
|
||||||
|
</Link>
|
||||||
|
<button
|
||||||
|
onClick={dismiss}
|
||||||
|
className="px-3 py-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { dashboardAPI } from "../api";
|
import { dashboardAPI } from "../api";
|
||||||
|
import { useAuthStore } from "@/stores/authStore";
|
||||||
|
|
||||||
export function useDashboard() {
|
export function useDashboard() {
|
||||||
|
const token = useAuthStore((s) => s.token);
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["dashboard"],
|
queryKey: ["dashboard", !!token],
|
||||||
queryFn: () => dashboardAPI.get(),
|
queryFn: () => dashboardAPI.get(),
|
||||||
staleTime: 2 * 60 * 1000,
|
staleTime: 2 * 60 * 1000,
|
||||||
refetchInterval: 5 * 60 * 1000,
|
refetchInterval: 5 * 60 * 1000,
|
||||||
|
|||||||
Reference in New Issue
Block a user