diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..3b24e4a1380dcde68e4e76fac939914c3479f65e
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,4 @@
+node_modules
+dist
+.env
+*.log
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..af3f68ace456b2024d02a5dc013e9cc12ed13dd9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+node_modules
+dist
+.env
+*.log
+.DS_Store
diff --git a/Dockerfile b/Dockerfile
index f0f1bbdacbcc8147a761b0c9a2560ffd5dd819f3..62ee85046fe124978b5b933f8b0b868b5e25aff8 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,40 +1,24 @@
-# Simple React App Deployment for HuggingFace Spaces
-FROM node:20-slim AS builder
+FROM node:20-slim
WORKDIR /app
-# Copy frontend source
-COPY frontend/package*.json ./
-RUN npm ci
+# Copy package files
+COPY package*.json ./
-COPY frontend/ ./
+# Install dependencies
+RUN npm ci --prefer-offline --no-audit
-# Build React app for production
-ENV VITE_CANONICAL_DOMAIN=communityone-readme.hf.space
-ENV VITE_API_URL=https://www.communityone.com/api
-RUN npm run build
-
-# Production stage with nginx
-FROM nginx:alpine
+# Copy source code
+COPY . .
-# Copy built React app
-COPY --from=builder /app/dist /usr/share/nginx/html
+# Build the app
+RUN npm run build
-# Create nginx config for SPA
-RUN echo 'server { \
- listen 7860; \
- root /usr/share/nginx/html; \
- index index.html; \
- location / { \
- try_files $uri $uri/ /index.html; \
- } \
-}' > /etc/nginx/conf.d/default.conf
+# Install a simple static server
+RUN npm install -g serve
+# Expose port 7860 (HuggingFace Spaces default)
EXPOSE 7860
-CMD ["nginx", "-g", "daemon off;"]
-ENV LOG_LEVEL=INFO
-ENV HF_SPACES=1
-
-# Use supervisor to run all services
-CMD ["/app/start.sh"]
+# Serve the built app
+CMD ["serve", "-s", "dist", "-l", "7860"]
diff --git a/README.md b/README.md
index 5f3fe1fdc372f70e322992964ebaa8793d5ead03..25d67bb1f9a2c31cd3d8283a76ca85029f27b6b5 100644
--- a/README.md
+++ b/README.md
@@ -1,86 +1,18 @@
---
title: CommunityOne
-emoji: 🏘️
+emoji: 🌐
colorFrom: blue
colorTo: green
-sdk: static
+sdk: docker
pinned: false
+app_port: 7860
---
-# 🏘️ CommunityOne
+# 🌐 CommunityOne
**The open path to everything local**
-Building tools to help communities understand and engage with local government, nonprofits, and civic processes.
+This is the official organization card for CommunityOne, showcasing our civic engagement platform and open datasets.
-## 🚀 What We Build
+Visit the live platform at **https://www.communityone.com**
-### [Open Navigator for Engagement](https://www.communityone.com)
-An open-source platform for exploring and tracking:
-
-- **90,000+ jurisdictions** (cities, counties, states)
-- **3M+ nonprofit organizations** (complete IRS registry)
-- **7,300+ state legislators** (all 50 states + DC + PR)
-- **100,000+ state bills** (legislative tracking with full text)
-- **Government meetings** with AI-extracted insights
-- **Budget analysis** (municipal, county, state spending)
-- **Public opinion data** (survey questions from Roper Center)
-
-## 📊 Open Datasets
-
-All our data is freely available on HuggingFace:
-
-- **[one-nonprofits-organizations](https://huggingface.co/datasets/CommunityOne/one-nonprofits-organizations)** - 3.9M nonprofits (IRS EO-BMF + ProPublica)
-- **[one-nonprofits-financials](https://huggingface.co/datasets/CommunityOne/one-nonprofits-financials)** - Form 990 financial data
-- **[one-meetings-calendar](https://huggingface.co/datasets/CommunityOne/one-meetings-calendar)** - Government meeting schedules
-- **[reference-jurisdictions-cities](https://huggingface.co/datasets/CommunityOne/reference-jurisdictions-cities)** - 19K+ U.S. cities
-- **[reference-jurisdictions-counties](https://huggingface.co/datasets/CommunityOne/reference-jurisdictions-counties)** - 3,144 U.S. counties
-
-[View all 60+ datasets →](https://huggingface.co/CommunityOne/datasets)
-
-## 🛠️ Technology Stack
-
-- **Frontend:** React + Vite + Tailwind CSS
-- **Backend:** FastAPI + PostgreSQL + Delta Lake
-- **AI/ML:** LangChain, OpenAI GPT-4, Anthropic Claude
-- **Data:** Apache Spark, DuckDB, Polars, Pandas
-- **Deployment:** HuggingFace Spaces, Docker
-
-## 🎯 Use Cases
-
-### For Advocates & Organizers
-- Track legislation on your issue (healthcare, education, housing)
-- Find nonprofits working in your community
-- Monitor government meetings and budgets
-- Identify engagement opportunities
-
-### For Researchers & Journalists
-- Analyze policy trends across states
-- Study nonprofit sector patterns
-- Track government spending priorities
-- Map community infrastructure
-
-### For Developers
-- Access clean, standardized civic data
-- Build on open APIs and datasets
-- Fork and extend the platform
-- Contribute to open source tools
-
-## 🔗 Links
-
-- **🌐 Live Application:** [www.communityone.com](https://www.communityone.com)
-- **📖 Documentation:** [www.communityone.com/docs](https://www.communityone.com/docs)
-- **💻 GitHub:** [getcommunityone/open-navigator-for-engagement](https://github.com/getcommunityone/open-navigator-for-engagement)
-- **📊 Datasets:** [All CommunityOne datasets](https://huggingface.co/CommunityOne/datasets)
-
-## 📝 License
-
-MIT License - Free to use, modify, and distribute
-
-## 🤝 Contributing
-
-We welcome contributions! Check out our [GitHub repository](https://github.com/getcommunityone/open-navigator-for-engagement) to get started.
-
----
-
-**Focus Areas:** Civic Tech • Open Data • Government Transparency • Nonprofit Sector • Legislative Tracking • Community Engagement
diff --git a/frontend/.env.example b/frontend/.env.example
deleted file mode 100644
index 83906c95207f5587de5980caa3141ea4a0097684..0000000000000000000000000000000000000000
--- a/frontend/.env.example
+++ /dev/null
@@ -1,15 +0,0 @@
-# Example environment variables for frontend development
-# Copy to .env.development.local to customize
-
-# Documentation URL (Docusaurus runs on port 3000 in development)
-# Default: http://localhost:3000
-# VITE_DOCS_URL=http://localhost:3000
-
-# API URL (FastAPI runs on port 8000)
-# Default: http://localhost:8000
-# VITE_API_URL=http://localhost:8000
-
-# Google Analytics Measurement ID (same as docs site)
-# Get from: https://analytics.google.com (GA4 property)
-# Format: G-XXXXXXXXXX
-VITE_GOOGLE_ANALYTICS_ID=G-5EQV815915
diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs
deleted file mode 100644
index d6c953795300e4256c76542d6bb0fe06f08b5ad6..0000000000000000000000000000000000000000
--- a/frontend/.eslintrc.cjs
+++ /dev/null
@@ -1,18 +0,0 @@
-module.exports = {
- root: true,
- env: { browser: true, es2020: true },
- extends: [
- 'eslint:recommended',
- 'plugin:@typescript-eslint/recommended',
- 'plugin:react-hooks/recommended',
- ],
- ignorePatterns: ['dist', '.eslintrc.cjs'],
- parser: '@typescript-eslint/parser',
- plugins: ['react-refresh'],
- rules: {
- 'react-refresh/only-export-components': [
- 'warn',
- { allowConstantExport: true },
- ],
- },
-}
diff --git a/frontend/.gitignore b/frontend/.gitignore
deleted file mode 100644
index c2fa92fe5994812219a6bd8f010d87523063491e..0000000000000000000000000000000000000000
--- a/frontend/.gitignore
+++ /dev/null
@@ -1,21 +0,0 @@
-# Frontend dependencies
-node_modules/
-dist/
-*.log
-.DS_Store
-
-# Build output
-api/static/
-
-# Environment
-.env
-.env.local
-
-# IDE
-.vscode/
-.idea/
-*.swp
-*.swo
-
-# Testing
-coverage/
diff --git a/frontend/README.md b/frontend/README.md
deleted file mode 100644
index f4662c1f8dcaf44a0f0fda97fb83139c4e01e515..0000000000000000000000000000000000000000
--- a/frontend/README.md
+++ /dev/null
@@ -1,166 +0,0 @@
-# Open Navigator for Engagement - Frontend
-
-React + TypeScript web interface for the Open Navigator for Engagement application.
-
-## Projects
-
-### Main Application
-React + TypeScript web interface with maps, charts, and data visualization.
-
-**Location:** `frontend/` (this directory)
-
-### Policy Accountability Dashboards
-Evidence-based accountability dashboards for policy advocacy.
-
-**Location:** `frontend/policy-dashboards/`
-**Documentation:** [Policy Dashboards README](policy-dashboards/README.md)
-
-## Tech Stack
-
-- **React 18.2** - UI framework
-- **TypeScript** - Type safety
-- **Vite** - Build tool
-- **Tailwind CSS** - Styling
-- **React Router** - Navigation
-- **TanStack Query** - Data fetching
-- **Recharts** - Charts
-- **Leaflet** - Interactive maps
-
-## Development
-
-```bash
-# Install dependencies
-npm install
-
-# Run dev server (hot reload)
-npm run dev
-# Opens http://localhost:3000
-
-# Build for production
-npm run build
-# Outputs to ../api/static/
-
-# Type check
-npm run type-check
-
-# Lint
-npm run lint
-```
-
-## Project Structure
-
-```
-src/
-├── components/
-│ └── Layout.tsx # Main layout with sidebar
-├── pages/
-│ ├── Dashboard.tsx # Statistics and charts
-│ ├── Heatmap.tsx # Interactive map
-│ ├── Documents.tsx # Document browser
-│ ├── Opportunities.tsx # Opportunity manager
-│ └── Settings.tsx # Configuration
-├── App.tsx # Root component with routing
-├── main.tsx # Entry point
-└── index.css # Global styles
-```
-
-## Pages
-
-### Dashboard (`/`)
-- Total documents, opportunities, states monitored
-- Topic distribution charts (bar and pie)
-- Recent opportunities table
-
-### Heatmap (`/heatmap`)
-- Interactive Leaflet map
-- Color-coded urgency markers
-- State and topic filters
-- Popup details
-
-### Documents (`/documents`)
-- Searchable document list
-- Pagination
-- Topic tags
-- Source links
-
-### Opportunities (`/opportunities`)
-- Filterable by urgency
-- Generate advocacy emails
-- Talking points display
-- Confidence scores
-
-### Settings (`/settings`)
-- Target state selection
-- Policy topic configuration
-- Notification preferences
-- Agent status monitoring
-
-## Environment
-
-The frontend proxies API requests to the backend:
-
-```typescript
-// vite.config.ts
-server: {
- proxy: {
- '/api': {
- target: 'http://localhost:8000',
- changeOrigin: true,
- },
- },
-}
-```
-
-## Building
-
-Production builds are output to `../api/static/` so the FastAPI backend can serve them:
-
-```typescript
-// vite.config.ts
-build: {
- outDir: '../api/static',
- emptyOutDir: true,
-}
-```
-
-## Styling
-
-Uses Tailwind CSS with custom utility classes:
-
-```css
-/* index.css */
-.card { @apply bg-white rounded-lg shadow-md p-6; }
-.btn-primary { @apply bg-primary-600 hover:bg-primary-700 text-white ... }
-.btn-secondary { @apply bg-gray-200 hover:bg-gray-300 text-gray-800 ... }
-```
-
-## Data Fetching
-
-Uses TanStack Query for server state management:
-
-```typescript
-const { data, isLoading } = useQuery({
- queryKey: ['opportunities', state, topic],
- queryFn: async () => {
- const response = await axios.get('/api/opportunities', { params: { state, topic } })
- return response.data
- },
-})
-```
-
-## Deployment
-
-The built frontend is automatically included when deploying to Databricks Apps:
-
-```bash
-# Build frontend
-npm run build
-
-# Deploy entire app
-cd ..
-./scripts/deploy-databricks-app.sh
-```
-
-## License
-
-MIT License - See LICENSE file for details
diff --git a/frontend/index.html b/frontend/index.html
deleted file mode 100644
index df66cfe6c50bc65899196adb7721faac0af35104..0000000000000000000000000000000000000000
--- a/frontend/index.html
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
- {/* Header */}
-
-
- {metadata.title}
-
-
- {metadata.description}
-
-
-
- {/* View Mode Toggle */}
-
-
-
setViewMode('home')}
- style={{
- padding: '8px 16px',
- borderRadius: 8,
- fontSize: 13,
- cursor: 'pointer',
- border: '1px solid',
- borderColor: viewMode === 'home' ? '#888' : '#ddd',
- background: viewMode === 'home' ? '#f5f5f2' : 'white',
- fontWeight: viewMode === 'home' ? 500 : 400,
- color: viewMode === 'home' ? '#111' : '#666',
- display: 'flex',
- alignItems: 'center',
- gap: 6
- }}
- >
-
- Home
-
-
setViewMode('browse')}
- style={{
- padding: '8px 16px',
- borderRadius: 8,
- fontSize: 13,
- cursor: 'pointer',
- border: '1px solid',
- borderColor: viewMode === 'browse' ? '#888' : '#ddd',
- background: viewMode === 'browse' ? '#f5f5f2' : 'white',
- fontWeight: viewMode === 'browse' ? 500 : 400,
- color: viewMode === 'browse' ? '#111' : '#666',
- display: 'flex',
- alignItems: 'center',
- gap: 6
- }}
- >
-
- Browse by Topic
-
-
setViewMode('dashboards')}
- style={{
- padding: '8px 16px',
- borderRadius: 8,
- fontSize: 13,
- cursor: 'pointer',
- border: '1px solid',
- borderColor: viewMode === 'dashboards' ? '#888' : '#ddd',
- background: viewMode === 'dashboards' ? '#f5f5f2' : 'white',
- fontWeight: viewMode === 'dashboards' ? 500 : 400,
- color: viewMode === 'dashboards' ? '#111' : '#666',
- display: 'flex',
- alignItems: 'center',
- gap: 6
- }}
- >
-
- Analysis Dashboards
-
-
setViewMode('decisions')}
- style={{
- padding: '8px 16px',
- borderRadius: 8,
- fontSize: 13,
- cursor: 'pointer',
- border: '1px solid',
- borderColor: viewMode === 'decisions' ? '#888' : '#ddd',
- background: viewMode === 'decisions' ? '#f5f5f2' : 'white',
- fontWeight: viewMode === 'decisions' ? 500 : 400,
- color: viewMode === 'decisions' ? '#111' : '#666',
- display: 'flex',
- alignItems: 'center',
- gap: 6
- }}
- >
-
- Alx',
- justifyContent: 'space-between',
- alignItems: 'center',
- marginBottom: 16
- }}>
-
-
setViewMode('dashboards')}
- style={{
- Topic Navigation (show in browse view) */}
- {viewMode === 'browse' && (
-
- )}
-
- {/* padding: '8px 16px',
- borderRadius: 8,
- fontSize: 13,
- cursor: 'pointer',
- border: '1px solid',
- borderColor: viewMode === 'dashboards' ? '#888' : '#ddd',
- background: viewMode === 'dashboards' ? '#f5f5f2' : 'white',
- fontWeight: viewMode === 'dashboards' ? 500 : 400,
- color: viewMode === 'dashboards' ? '#111' : '#666',
- display: 'flex',
- alignItems: 'center',
- gap: 6
- }}
- >
-
- Dashboards
-
-
setViewMode('decisions')}
- style={{
- padding: '8px 16px',
- borderRadius: 8,
- fontSize: 13,
- cursor: 'pointer',
- border: '1px solid',
- borderColor: viewMode === 'decisions' ? '#888' : '#ddd',
- background: viewMode === 'decisions' ? '#f5f5f2' : 'white',
- fontWeight: viewMode === 'decisions' ? 500 : 400,
- color: viewMode === 'decisions' ? '#111' : '#666',
- display: 'flex',
- alignIthome' ? (
-
- ) : viewMode === 'impact' ? (
-
-
- ← Back to Home
-
-
-
- ) : viewMode === 'browse' ? (
-
-
- Browse Decisions by Topic
-
-
- {filteredDecisions.length === 0 ? (
-
-
- No decisions match your filters
-
-
- Clear filters
-
-
- ) : (
- <>
-
- {filteredDecisions.length} decision{filteredDecisions.length !== 1 ? 's' : ''} found
-
- {filteredDecisions.map((decision, i) => (
-
console.log('View decision:', decision)}
- />
- ))}
- >
- )}
-
- ) : viewMode === 'ems: 'center',
- gap: 6
- }}
- >
-
- Individual Decisions
-
-
-
-
- {/* Filters (show in decisions view) */}
- {viewMode === 'decisions' && (
-
- )}
-
- {/* Tab Navigation (show in dashboard view) */}
- {viewMode === 'dashboards' && (
-
- {tabs.map((t) => (
- setActive(t.id)}
- style={{
- padding: '6px 14px',
- borderRadius: 8,
- fontSize: 13,
- cursor: 'pointer',
- border: '1px solid',
- borderColor: active === t.id ? '#888' : '#ddd',
- background: active === t.id ? '#f5f5f2' : 'white',
- fontWeight: active === t.id ? 500 : 400,
- color: active === t.id ? '#111' : '#666',
- transition: 'all 0.2s ease'
- }}
- >
- {t.label}
-
- ))}
-
- )}
-
- {/* Content */}
- {viewMode === 'dashboards' ? (
-
-
- {tabs[active].label}
-
-
- {active === 0 ? (
-
- ) : (
-
- )}
-
- ) : (
-
-
-
- Individual Decisions
-
-
- {filteredDecisions.length} decision{filteredDecisions.length !== 1 ? 's' : ''}
-
-
-
- {filteredDecisions.length === 0 ? (
-
-
- No decisions match your filters
-
-
- Clear filters
-
-
- ) : (
- filteredDecisions.map((decision, i) => (
-
{
- // Could open modal or navigate to detail view
- console.log('View decision details:', decision);
- }}
- />
- ))
- )}
-
- )}
-
- {/* Footer */}
-
-
- Generated by Oral Health Policy Pulse — Evidence-based advocacy toolkit
-
-
- Methodology: Accountability dashboards using public meeting records and budget data
-
-
-
- );
-}
diff --git a/frontend/policy-dashboards/src/components/EndlessStudyLoop.jsx b/frontend/policy-dashboards/src/components/EndlessStudyLoop.jsx
deleted file mode 100644
index dd780f6d881fd6dc47621fda8e7950f1ac5efaab..0000000000000000000000000000000000000000
--- a/frontend/policy-dashboards/src/components/EndlessStudyLoop.jsx
+++ /dev/null
@@ -1,162 +0,0 @@
-import React from 'react';
-import MetricCard from './shared/MetricCard';
-import Compare from './shared/Compare';
-import InsightBox from './shared/InsightBox';
-import { logicChainData as d } from '../data/dashboardData';
-
-/**
- * Dashboard 2: Delayed 6 months and counting
- * (Logic Chain / Sequential Deferral)
- */
-export default function EndlessStudyLoop() {
- return (
-
-
- {d.conclusion}
-
-
- {/* Topic */}
-
-
- Policy Decision
-
-
- {d.topic}
-
-
-
- {/* Key Metrics */}
-
-
- j.reason)).size}
- label="Distinct justifications used"
- />
-
-
- {/* The "Study" Loop Timeline */}
-
-
- Shifting Justifications
-
-
-
- {/* Vertical timeline line */}
-
-
- {d.justifications.map((item, i) => (
-
- {/* Timeline dot */}
-
-
- {/* Timeline content */}
-
- {item.month} — {item.status}
-
-
- "{item.reason}"
-
- {item.speaker && item.speaker !== 'N/A' && (
-
- — {item.speaker}
-
- )}
-
- ))}
-
-
-
- {/* Benchmark Comparison */}
-
-
- School-Linked Dental Programs by State Type
-
-
-
- Source: ASTDD State Oral Health Program Database, 2025
-
-
-
- {/* The Logic */}
-
- {d.patternType}: {d.inference}
-
-
- {/* Question for the Room */}
-
-
Ask them:
-
- "This proposal has been 'under review' for {d.monthsInLimbo} months with {d.totalDeferrals} deferrals.
- Each time, you give a different reason. {d.benchmarks.republicanAvg.activePrograms} Republican-led states
- and {d.benchmarks.democraticAvg.activePrograms} Democratic-led states already have active programs.
- What analysis are you waiting for that 35 states haven't already completed?"
-
-
-
- );
-}
diff --git a/frontend/policy-dashboards/src/components/HomePage.jsx b/frontend/policy-dashboards/src/components/HomePage.jsx
deleted file mode 100644
index f20b4d3ef399da599741c80cc62aededf5ec454f..0000000000000000000000000000000000000000
--- a/frontend/policy-dashboards/src/components/HomePage.jsx
+++ /dev/null
@@ -1,291 +0,0 @@
-import React from 'react';
-import { TrendingUp, AlertCircle, Users, Scale, Droplet, Building2, Heart, Church, Grid } from 'lucide-react';
-
-/**
- * HomePage Component - Policy Accountability Platform
- * Sector-based landing page for citizens
- */
-export default function HomePage({ onPersonaSelect, onTopicSelect, onSectorSelect }) {
- return (
-
- {/* Top Section: Explore by Sector */}
-
-
-
- Policy Accountability Platform
-
-
- Track Government Decisions & Community Solutions
-
-
- Explore policy decisions, track accountability, and discover community resources
-
-
-
- {/* Sector Navigation Cards */}
-
- }
- title="All Sectors"
- description="View everything together"
- color="#059669"
- onClick={() => onSectorSelect('all')}
- />
- }
- title="Public Sector"
- description="Government decisions"
- color="#185FA5"
- onClick={() => onSectorSelect('public')}
- />
- }
- title="Nonprofits"
- description="Community organizations"
- color="#059669"
- onClick={() => onSectorSelect('nonprofits')}
- />
- }
- title="Churches"
- description="Faith-based ministries"
- color="#A855F7"
- onClick={() => onSectorSelect('churches')}
- />
-
-
-
- {/* Middle Section: Find Your Impact (Persona Filters) */}
-
-
- Find Your Impact
-
-
- How are these decisions affecting you?
-
-
-
-
onPersonaSelect('parent', 'dental-health')}
- />
- onPersonaSelect('advocate', 'transparency')}
- />
- onPersonaSelect('resident', 'water-infrastructure')}
- />
-
-
-
- {/* Bottom Section: Topic Navigation */}
-
-
- Browse by Topic
-
-
-
- }
- title="Public Health"
- subtitle="Dental, Water, Mental Health"
- count={24}
- color="#1D9E75"
- onClick={() => onTopicSelect('public-health')}
- />
- }
- title="Education & Youth"
- subtitle="School Board, Pre-K"
- count={18}
- color="#185FA5"
- onClick={() => onTopicSelect('education')}
- />
- }
- title="Infrastructure"
- subtitle="Roads, Utilities, Construction"
- count={32}
- color="#BA7517"
- onClick={() => onTopicSelect('infrastructure')}
- />
- }
- title="Public Safety"
- subtitle="Police, Fire, EMS"
- count={15}
- color="#E24B4A"
- onClick={() => onTopicSelect('public-safety')}
- />
-
-
-
- );
-}
-
-function SectorCard({ icon, title, description, color, onClick }) {
- return (
-
{
- e.currentTarget.style.background = 'rgba(255, 255, 255, 0.15)';
- e.currentTarget.style.borderColor = color;
- e.currentTarget.style.transform = 'translateY(-4px)';
- }}
- onMouseLeave={(e) => {
- e.currentTarget.style.background = 'rgba(255, 255, 255, 0.1)';
- e.currentTarget.style.borderColor = 'rgba(255, 255, 255, 0.2)';
- e.currentTarget.style.transform = 'translateY(0)';
- }}
- >
-
- {icon}
-
-
- {title}
-
-
- {description}
-
-
- );
-}
-
-function PersonaCard({ icon, persona, concern, action, onClick }) {
- return (
-
{
- e.currentTarget.style.borderColor = '#D85A30';
- e.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,0.1)';
- }}
- onMouseLeave={(e) => {
- e.currentTarget.style.borderColor = '#eee';
- e.currentTarget.style.boxShadow = 'none';
- }}
- >
-
{icon}
-
-
- I am a...
-
-
- {persona}
-
-
-
-
- I care about...
-
-
- {concern}
-
-
-
- Show me → {action}
-
-
- );
-}
-
-function TopicCard({ icon, title, subtitle, count, color, onClick }) {
- return (
-
{
- e.currentTarget.style.borderColor = color;
- e.currentTarget.style.transform = 'translateY(-2px)';
- e.currentTarget.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)';
- }}
- onMouseLeave={(e) => {
- e.currentTarget.style.borderColor = '#eee';
- e.currentTarget.style.transform = 'translateY(0)';
- e.currentTarget.style.boxShadow = 'none';
- }}
- >
-
{icon}
-
-
- {title}
-
-
- {subtitle}
-
-
-
- {count}
-
-
- );
-}
diff --git a/frontend/policy-dashboards/src/components/ImpactDashboard.jsx b/frontend/policy-dashboards/src/components/ImpactDashboard.jsx
deleted file mode 100644
index 224ebd2e1cbfcee7edfbaff138cd4b107fc86adb..0000000000000000000000000000000000000000
--- a/frontend/policy-dashboards/src/components/ImpactDashboard.jsx
+++ /dev/null
@@ -1,280 +0,0 @@
-import React from 'react';
-import { XCircle, AlertTriangle, MapPin } from 'lucide-react';
-
-/**
- * ImpactDashboard Component
- * Visual impact story for specific persona + topic combination
- */
-export default function ImpactDashboard({ persona, topic }) {
- // Example: Parent + Dental Health
- if (persona === 'parent' && topic === 'dental-health') {
- return
;
- }
-
- // Example: Advocate + Transparency
- if (persona === 'advocate' && topic === 'transparency') {
- return
;
- }
-
- // Default fallback
- return
;
-}
-
-function DentalHealthImpact() {
- return (
-
- {/* Header */}
-
-
- Impact Story: Parent → Student Dental Health
-
-
- The School Dental Screening Veto
-
-
- A legal "Risk Rationale" is blocking healthcare for 5,000 students
-
-
-
- {/* The Visual Split */}
-
- {/* Left: The Human Cost */}
-
-
-
-
- The Human Cost
-
-
-
- {/* School Map Placeholder */}
-
-
-
- Map of Tuscaloosa Schools
-
-
- Red = High dental pain absence rates
-
-
- Blue dots = Mobile clinics (currently: 0)
-
-
-
- {/* Stats */}
-
-
-
- 48%
-
-
- Students with no dental visit last year
-
-
-
-
- 127
-
-
- Days missed due to dental pain (2025)
-
-
-
-
-
- {/* Right: The Veto */}
-
-
-
-
- The Veto Chain
-
-
-
- {/* Flowchart */}
-
- {/* Step 1: Public Demand */}
-
-
- ✓ Public Demand
-
-
- 1,200 Signed Petitions
-
-
- 240+ Testimonies at board meetings
-
-
-
- {/* Arrow */}
-
- ↓ BLOCKED
-
-
- {/* Step 2: The Blocker */}
-
-
- ✗ The Blocker
-
-
- Risk Management Memo
-
-
- From: Patricia Johnson, District Legal Office
-
-
- Concern: "Insurance Liability"
-
-
-
- {/* Arrow */}
-
- ↓
-
-
- {/* Step 3: The Result */}
-
-
- The Result
-
-
- Board votes to "Table" initiative
-
-
- Status: Deferred for 152 days
-
-
-
-
-
-
- {/* Bottom: Key Fact */}
-
-
-
-
- Key Finding
-
-
- Zero successful liability lawsuits in any of the 35 states with active school dental screening programs.
- The "risk" cited in the memo has no empirical basis.
-
-
-
-
- );
-}
-
-function TransparencyImpact() {
- return (
-
-
-
- Impact Story: Advocate → Transparency & Vetoes
-
-
- Who Really Decides?
-
-
- Unelected staff have veto power that outweighs public input
-
-
-
-
- See the Influence Radar dashboard for detailed analysis →
-
-
- );
-}
-
-function GenericImpact({ persona, topic }) {
- return (
-
-
-
- Impact Story: {persona} → {topic}
-
-
- Coming Soon
-
-
- This impact story is being developed. Check back soon or browse other topics.
-
-
-
- );
-}
diff --git a/frontend/policy-dashboards/src/components/NonprofitCard.jsx b/frontend/policy-dashboards/src/components/NonprofitCard.jsx
deleted file mode 100644
index 17b9c9eed550748a6e820ce4a678f480964047ed..0000000000000000000000000000000000000000
--- a/frontend/policy-dashboards/src/components/NonprofitCard.jsx
+++ /dev/null
@@ -1,290 +0,0 @@
-import React from 'react';
-import { Heart, Mail, Phone, Globe, Users, DollarSign } from 'lucide-react';
-
-/**
- * NonprofitCard Component
- * Displays individual nonprofit or church organization
- */
-export default function NonprofitCard({ nonprofit, isChurch = false }) {
- const {
- name,
- ein,
- ntee_code,
- ntee_description,
- mission,
- services = [],
- annual_budget,
- students_served,
- families_served,
- youth_served,
- contact,
- volunteer_opportunities,
- accepting_board_members
- } = nonprofit;
-
- return (
-
- {/* Header */}
-
-
-
- {name}
-
- {isChurch && (
-
- ⛪ Faith-Based
-
- )}
-
-
-
- NTEE {ntee_code}: {ntee_description}
- {ein && • EIN: {ein} }
-
-
-
- {mission}
-
-
-
- {/* Services */}
- {services.length > 0 && (
-
-
- Services Provided:
-
-
- {services.map((service, i) => (
- {service}
- ))}
-
-
- )}
-
- {/* Impact Metrics */}
-
- {students_served > 0 && (
-
-
Students Served
-
-
- {students_served.toLocaleString()}
-
-
- )}
- {families_served > 0 && (
-
-
Families Served
-
-
- {families_served.toLocaleString()}
-
-
- )}
- {youth_served > 0 && (
-
-
Youth Served
-
-
- {youth_served.toLocaleString()}
-
-
- )}
-
-
Annual Budget
-
-
- {(annual_budget / 1000).toFixed(0)}K
-
-
-
-
- {/* Contact & Actions */}
-
-
- {/* Opportunities */}
-
- {volunteer_opportunities && (
-
- ✓ Accepting Volunteers
-
- )}
- {accepting_board_members && (
-
- ⭐ Board Seats Available
-
- )}
-
-
- );
-}
diff --git a/frontend/policy-dashboards/src/components/SplitScreenView.jsx b/frontend/policy-dashboards/src/components/SplitScreenView.jsx
deleted file mode 100644
index 8c5488f2385e2ae40e99e08a3dffbba995e26299..0000000000000000000000000000000000000000
--- a/frontend/policy-dashboards/src/components/SplitScreenView.jsx
+++ /dev/null
@@ -1,375 +0,0 @@
-import React from 'react';
-import { ExternalLink, Users, DollarSign, Heart, Mail, Phone, Globe } from 'lucide-react';
-
-/**
- * SplitScreenView Component
- * Shows government decisions on the left, community nonprofits on the right
- * Demonstrates the accountability gap and community response
- */
-export default function SplitScreenView({ decision, nonprofits = [] }) {
- // Find nonprofits matching this decision's NTEE code
- const matchingNonprofits = nonprofits.filter(np =>
- np.ntee_code === decision.ntee_code ||
- (decision.ntee_code && np.ntee_code?.startsWith(decision.ntee_code[0])) // Match category
- );
-
- const hasGap = decision.community_gap?.nonprofit_filling_gap;
-
- return (
-
- {/* LEFT RAIL: The Public Sector (Government Decision) */}
-
-
- 🏛️ Public Sector Decision
-
-
-
- {decision.decision_summary}
-
-
-
-
- Official Rationale:
-
-
- "{decision.primary_rationale}"
-
-
-
- {hasGap && (
-
-
- ⚠️ The Accountability Gap
-
-
- {decision.community_gap.description}
-
-
- )}
-
-
-
Outcome: {decision.outcome}
-
Vote: {decision.vote_result}
-
Date: {new Date(decision.meeting_date).toLocaleDateString()}
-
-
-
- {/* RIGHT RAIL: The Community Sector (Nonprofits) */}
-
-
- 🤝 Community Sector Response
-
-
- {matchingNonprofits.length === 0 ? (
-
-
- No nonprofits found filling this gap yet.
-
-
- NTEE Code: {decision.ntee_code || 'Not classified'}
-
-
- ) : (
- <>
-
- {matchingNonprofits.length} organization{matchingNonprofits.length !== 1 ? 's' : ''} filling this gap:
-
-
-
- {matchingNonprofits.map((nonprofit, i) => (
-
- ))}
-
-
-
-
- 💡 Bridge the Gap
-
-
- Support these organizations with donations or volunteering
- Join their boards to influence systemic change
- Cite their work in public meetings to show solutions exist
-
-
- >
- )}
-
-
- );
-}
-
-function NonprofitCard({ nonprofit }) {
- return (
-
-
-
- {nonprofit.name}
-
-
- NTEE {nonprofit.ntee_code}: {nonprofit.ntee_description}
-
-
- {nonprofit.mission}
-
-
-
- {/* Services */}
-
-
- Services Provided:
-
-
- {nonprofit.services.slice(0, 3).map((service, i) => (
- {service}
- ))}
-
-
-
- {/* Impact */}
-
- {nonprofit.students_served && (
-
-
Impact
-
-
- {nonprofit.students_served.toLocaleString()} students
-
-
- )}
- {nonprofit.families_served && (
-
-
Impact
-
-
- {nonprofit.families_served.toLocaleString()} families
-
-
- )}
- {nonprofit.youth_served && (
-
-
Impact
-
-
- {nonprofit.youth_served.toLocaleString()} youth
-
-
- )}
-
-
Annual Budget
-
-
- {(nonprofit.annual_budget / 1000).toFixed(0)}K
-
-
-
-
- {/* Contact & Actions */}
-
-
- {/* Opportunities */}
-
- {nonprofit.volunteer_opportunities && (
-
- ✓ Accepting Volunteers
-
- )}
- {nonprofit.accepting_board_members && (
-
- ⭐ Board Seats Available
-
- )}
-
-
- );
-}
diff --git a/frontend/policy-dashboards/src/components/Summary.jsx b/frontend/policy-dashboards/src/components/Summary.jsx
deleted file mode 100644
index 89cea3b70ac34d8c8c8d67072de210c2339f551e..0000000000000000000000000000000000000000
--- a/frontend/policy-dashboards/src/components/Summary.jsx
+++ /dev/null
@@ -1,183 +0,0 @@
-import React from 'react';
-import { summaryData as d } from '../data/dashboardData';
-import { DashboardGrid } from './shared/DashboardTile';
-
-/**
- * Summary Dashboard
- * Overview of all four findings with tile-based navigation
- */
-export default function Summary({ onNavigate }) {
- return (
-
- {/* Headline */}
-
-
- {d.headline}
-
-
- {d.subheadline}
-
-
-
- {/* Dashboard Tiles */}
-
-
- {/* Legacy Finding Cards - Keep for compact view */}
-
-
- Show compact list view
-
-
- {d.findings.map((finding, i) => (
-
onNavigate && onNavigate(finding.id)}
- style={{
- background: '#fff',
- border: '1px solid #eee',
- borderLeft: `4px solid ${finding.discomfort >= 9 ? '#D85A30' : '#BA7517'}`,
- borderRadius: 8,
- padding: 16,
- cursor: onNavigate ? 'pointer' : 'default',
- transition: 'all 0.2s ease'
- }}
- onMouseEnter={(e) => {
- if (onNavigate) {
- e.currentTarget.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)';
- e.currentTarget.style.borderLeftWidth = '6px';
- }
- }}
- onMouseLeave={(e) => {
- if (onNavigate) {
- e.currentTarget.style.boxShadow = 'none';
- e.currentTarget.style.borderLeftWidth = '4px';
- }
- }}
- >
-
-
-
- Dashboard {finding.id}
-
-
- {finding.title}
-
-
-
-
= 9 ? '#D85A30' : '#BA7517'
- }}>
- {finding.metric}
-
-
- {finding.context}
-
-
-
-
- {finding.summary}
-
- {finding.discomfort >= 9 && (
-
- ⚠️ High accountability impact
-
- )}
-
- ))}
-
-
-
- {/* How to Use Section */}
-
-
- {d.howToUse.title}
-
-
-
- {d.howToUse.strategies.map((strategy, i) => (
-
-
-
- ❌ DON'T: {strategy.dont}
-
-
-
-
- ✅ DO: {strategy.do}
-
-
-
- ))}
-
-
-
- {/* Bottom Navigation Hint */}
- {onNavigate && (
-
- 💡 Click any finding above to see the detailed dashboard
-
- )}
-
- );
-}
diff --git a/frontend/policy-dashboards/src/components/TopicNavigation.jsx b/frontend/policy-dashboards/src/components/TopicNavigation.jsx
deleted file mode 100644
index b0ae6d3b098041ddeed2c9474983e7e50533676a..0000000000000000000000000000000000000000
--- a/frontend/policy-dashboards/src/components/TopicNavigation.jsx
+++ /dev/null
@@ -1,511 +0,0 @@
-import React, { useState } from 'react';
-import { Filter, Video, FileText, DollarSign, BarChart3 } from 'lucide-react';
-
-/**
- * TopicNavigation Component
- * Primary and secondary filters for browsing decisions
- */
-export default function TopicNavigation({
- selectedTopics = [],
- selectedPatterns = [],
- selectedResources = [],
- startDate,
- endDate,
- onTopicToggle,
- onPatternToggle,
- onResourceToggle,
- onStartDateChange,
- onEndDateChange,
- onClearAll
-}) {
- const [showFilters, setShowFilters] = useState(true);
-
- const topics = [
- { id: 'public-health', label: 'Public Health', sublabel: 'Dental, Water, Mental Health', color: '#1D9E75' },
- { id: 'education', label: 'Education & Youth', sublabel: 'School Board, Pre-K', color: '#185FA5' },
- { id: 'infrastructure', label: 'Infrastructure', sublabel: 'Roads, Utilities, Construction', color: '#BA7517' },
- { id: 'public-safety', label: 'Public Safety', sublabel: 'Police, Fire, EMS', color: '#E24B4A' }
- ];
-
- const patterns = [
- { id: 'technocratic-veto', label: 'Technocratic Veto', description: 'Legal/risk managers blocking decisions' },
- { id: 'sequential-deferral', label: 'Sequential Deferral', description: 'Repeated "tabling for study"' },
- { id: 'performance-rationale', label: 'Performance Rationale', description: 'Rhetoric not matching funding' }
- ];
-
- const resources = [
- { id: 'video', label: 'Video Recap', icon: Video },
- { id: 'budget', label: 'Budget PDF', icon: DollarSign },
- { id: 'dashboard', label: 'Impact Dashboard', icon: BarChart3 },
- { id: 'summary', label: 'Summary Notes', icon: FileText }
- ];
-
- const hasActiveFilters = selectedTopics.length > 0 ||
- selectedPatterns.length > 0 ||
- selectedResources.length > 0 ||
- startDate !== null ||
- endDate !== null;
-
- return (
-
- {/* Header */}
-
- setShowFilters(!showFilters)}
- style={{
- padding: '10px 18px',
- border: '1px solid',
- borderColor: showFilters ? '#888' : '#ddd',
- borderRadius: 8,
- background: showFilters ? '#f5f5f2' : 'white',
- cursor: 'pointer',
- fontSize: 15,
- fontWeight: 500,
- display: 'flex',
- alignItems: 'center',
- gap: 6,
- color: showFilters ? '#222' : '#666'
- }}
- >
-
- Filter & Browse
- {hasActiveFilters && (
-
- {selectedTopics.length + selectedPatterns.length + selectedResources.length}
-
- )}
-
-
- {hasActiveFilters && (
-
- Clear All Filters
-
- )}
-
-
- {/* Filter Panel */}
- {showFilters && (
-
- {/* Primary Navigation: Topics */}
-
-
- Primary Topic / Domain
-
-
- {topics.map(topic => {
- const isSelected = selectedTopics.includes(topic.id);
- return (
-
onTopicToggle(topic.id)}
- style={{
- padding: '12px 16px',
- borderRadius: 8,
- border: '2px solid',
- borderColor: isSelected ? topic.color : '#ddd',
- background: isSelected ? `${topic.color}15` : 'white',
- color: '#222',
- fontSize: 15,
- cursor: 'pointer',
- textAlign: 'left',
- fontWeight: isSelected ? 500 : 400,
- transition: 'all 0.2s ease'
- }}
- >
- {topic.label}
-
- {topic.sublabel}
-
-
- );
- })}
-
-
-
- {/* Secondary Filter: Patterns */}
-
-
- Filter by Pattern
-
-
- {patterns.map(pattern => {
- const isSelected = selectedPatterns.includes(pattern.id);
- return (
-
- onPatternToggle(pattern.id)}
- style={{ marginTop: 2 }}
- />
-
-
- {pattern.label}
-
-
- {pattern.description}
-
-
-
- );
- })}
-
-
-
- {/* Tertiary Filter: Resources */}
-
-
- Filter by Resource
-
-
- {resources.map(resource => {
- const isSelected = selectedResources.includes(resource.id);
- const Icon = resource.icon;
- return (
-
- onResourceToggle(resource.id)}
- />
-
-
- {resource.label}
-
-
- );
- })}
-
-
-
- {/* Time Window Filter */}
-
-
- Time Window
-
-
-
-
- From
-
- onStartDateChange(e.target.value || null)}
- style={{
- width: '100%',
- padding: '6px 8px',
- borderRadius: 6,
- border: '1px solid',
- borderColor: startDate ? '#D85A30' : '#ddd',
- fontSize: 12,
- background: 'white'
- }}
- />
-
-
-
- To
-
- onEndDateChange(e.target.value || null)}
- style={{
- width: '100%',
- padding: '6px 8px',
- borderRadius: 6,
- border: '1px solid',
- borderColor: endDate ? '#D85A30' : '#ddd',
- fontSize: 12,
- background: 'white'
- }}
- />
-
-
-
-
- )}
-
- {/* Active Filters Display */}
- {hasActiveFilters && (
-
- Active filters:
- {selectedTopics.map(topicId => {
- const topic = topics.find(t => t.id === topicId);
- return (
-
- {topic.label}
- onTopicToggle(topicId)}
- style={{
- background: 'none',
- border: 'none',
- color: 'white',
- cursor: 'pointer',
- padding: 0,
- fontSize: 14,
- lineHeight: 1
- }}
- >
- ×
-
-
- );
- })}
- {selectedPatterns.map(patternId => {
- const pattern = patterns.find(p => p.id === patternId);
- return (
-
- {pattern.label}
- onPatternToggle(patternId)}
- style={{
- background: 'none',
- border: 'none',
- color: 'white',
- cursor: 'pointer',
- padding: 0,
- fontSize: 14,
- lineHeight: 1
- }}
- >
- ×
-
-
- );
- })}
- {selectedResources.map(resourceId => {
- const resource = resources.find(r => r.id === resourceId);
- return (
-
- {resource.label}
- onResourceToggle(resourceId)}
- style={{
- background: 'none',
- border: 'none',
- color: 'white',
- cursor: 'pointer',
- padding: 0,
- fontSize: 14,
- lineHeight: 1
- }}
- >
- ×
-
-
- );
- })}
- {startDate && (
-
- From: {new Date(startDate).toLocaleDateString()}
- onStartDateChange(null)}
- style={{
- background: 'none',
- border: 'none',
- color: 'white',
- cursor: 'pointer',
- padding: 0,
- fontSize: 14,
- lineHeight: 1
- }}
- >
- ×
-
-
- )}
- {endDate && (
-
- To: {new Date(endDate).toLocaleDateString()}
- onEndDateChange(null)}
- style={{
- background: 'none',
- border: 'none',
- color: 'white',
- cursor: 'pointer',
- padding: 0,
- fontSize: 14,
- lineHeight: 1
- }}
- >
- ×
-
-
- )}
-
- )}
-
- );
-}
diff --git a/frontend/policy-dashboards/src/components/WhereMoneyWent.jsx b/frontend/policy-dashboards/src/components/WhereMoneyWent.jsx
deleted file mode 100644
index 52f250bca6a793ae4f6206c0165f90b72bee554c..0000000000000000000000000000000000000000
--- a/frontend/policy-dashboards/src/components/WhereMoneyWent.jsx
+++ /dev/null
@@ -1,162 +0,0 @@
-import React from 'react';
-import Compare from './shared/Compare';
-import InsightBox from './shared/InsightBox';
-import { displacementData as d } from '../data/dashboardData';
-
-/**
- * Dashboard 3: What got funded instead
- * (Displacement Matrix)
- */
-export default function WhereMoneyWent() {
- return (
-
-
- {d.conclusion}
-
-
- {/* Topic */}
-
-
- Budget Cycle
-
-
- {d.topic}
-
-
-
- {/* The Matrix Table */}
-
-
-
- {['Funded (winner)', 'Stagnant (loser)', 'Trade-off factor'].map(h => (
-
- {h}
-
- ))}
-
-
-
- {d.displacements.map((row, i) => (
-
-
- {row.winner}
- {row.winnerAmount && ` — $${(row.winnerAmount / 1000).toFixed(0)}k`}
-
-
- {row.loser}
- {row.loserAmount > 0 && ` — $${(row.loserAmount / 1000).toFixed(0)}k`}
-
-
-
- {row.tradeoffFactor}
-
-
-
- ))}
-
-
-
- {/* Per-Student Spending Breakdown */}
-
-
- Health Capital Spending (Per Student)
-
-
-
-
-
-
- Athletic Capital Spending (Per Student)
-
-
-
- Source: NCES F-33 Survey, Capital Outlay by Function, FY2025
-
-
-
- {/* The Logic */}
-
- {d.priorityPattern}: {d.inference}
-
-
- {/* Question for the Room */}
-
-
Ask them:
-
- "This budget year, you spent $
- {(d.displacements[0].winnerAmount / 1000).toFixed(0)}k on {d.displacements[0].winner.toLowerCase()}
- and $0 on {d.displacements[0].loser.toLowerCase()}. Can you explain why turf is worth more than
- the dental health of 5,000 students?"
-
-
-
- );
-}
diff --git a/frontend/policy-dashboards/src/components/WhoIsInCharge.jsx b/frontend/policy-dashboards/src/components/WhoIsInCharge.jsx
deleted file mode 100644
index 24ebcd6514e60e3972d6d6bc2c876bdf2bf07cb1..0000000000000000000000000000000000000000
--- a/frontend/policy-dashboards/src/components/WhoIsInCharge.jsx
+++ /dev/null
@@ -1,162 +0,0 @@
-import React from 'react';
-import BarMeter from './shared/BarMeter';
-import MetricCard from './shared/MetricCard';
-import Compare from './shared/Compare';
-import InsightBox from './shared/InsightBox';
-import { influenceData as d } from '../data/dashboardData';
-
-const colors = { blocker: '#E24B4A', public: '#185FA5' };
-
-/**
- * Dashboard 4: One memo beat 240 residents
- * (Influence Radar)
- */
-export default function WhoIsInCharge() {
- return (
-
-
- {d.conclusion}
-
-
- {/* Topic */}
-
-
- Policy Decision
-
-
- {d.topic}
-
-
-
- {/* Influence Bars */}
-
-
- Influence on Final Decision
-
-
- {d.actors.map((item, i) => (
-
-
-
- {item.contactName && `Contact: ${item.contactName}`}
-
-
- ))}
-
-
- {/* Key Metrics */}
-
-
-
-
-
- {/* Veto Holder Callout */}
-
-
- Effective Veto Holder
-
-
- {d.vetoHolder}
-
-
- One liability memo had {d.actors.find(a => a.type === 'blocker').influence}% influence
- despite {d.publicComments}+ citizen testimonies
-
-
-
- {/* Liability Benchmark */}
-
-
- Successful Liability Suits in States with Screening Programs
-
-
-
- Source: National Association of School Nurses, ADA Health Policy Institute
-
-
-
- {/* The Logic */}
-
- {d.powerStructure}: {d.inference}
-
-
- {/* Question for the Room */}
-
-
Ask them:
-
- "{d.vetoHolder}, can you please stand and explain to these {d.publicComments} citizens
- why your one memo expressing 'liability concerns' outweighed their collective voice?
- And can you cite a single successful lawsuit in any of the 35 states with active
- school dental screening programs?"
-
-
-
- );
-}
diff --git a/frontend/policy-dashboards/src/components/WordsVsDollars.jsx b/frontend/policy-dashboards/src/components/WordsVsDollars.jsx
deleted file mode 100644
index 7e9c09a03764a88f3fbea0cfc7a508f88fc99f76..0000000000000000000000000000000000000000
--- a/frontend/policy-dashboards/src/components/WordsVsDollars.jsx
+++ /dev/null
@@ -1,151 +0,0 @@
-import React from 'react';
-import BarMeter from './shared/BarMeter';
-import MetricCard from './shared/MetricCard';
-import Compare from './shared/Compare';
-import InsightBox from './shared/InsightBox';
-import { rhetoricGapData as d } from '../data/dashboardData';
-
-/**
- * Dashboard 1: They cut health spending while praising wellness
- * (Rhetoric Gap Monitor)
- */
-export default function WordsVsDollars() {
- return (
-
-
- {d.conclusion}
-
-
- {/* Key Metrics */}
-
-
-
-
-
- {/* What They SAY */}
-
-
- What They SAY
-
-
-
-
Sample quotes ({d.totalMentions} total mentions):
- {d.sampleQuotes.slice(0, 2).map((quote, i) => (
-
"{quote}"
- ))}
-
-
-
- {/* What They FUND */}
-
-
- What They FUND
-
-
-
-
-
- {/* Benchmark Comparison */}
-
-
- Per-Student Health Spending Comparison
-
-
-
- Source: NCES Common Core of Data (CCD), FY2025
-
-
-
- {/* The Logic */}
-
- {d.inference}
-
-
- {/* Question for the Room */}
-
-
Ask them:
-
- "You've praised student wellness {d.totalMentions} times this year with {d.sentimentScore}%
- positive sentiment. But you cut the health budget by ${Math.abs(d.budgetDelta).toLocaleString()}.
- Which statement is true: your words or your wallet?"
-
-
-
- );
-}
diff --git a/frontend/policy-dashboards/src/components/shared/BarMeter.jsx b/frontend/policy-dashboards/src/components/shared/BarMeter.jsx
deleted file mode 100644
index 9600590390b206639bb8eb508f168dd441fc95ca..0000000000000000000000000000000000000000
--- a/frontend/policy-dashboards/src/components/shared/BarMeter.jsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import React from 'react';
-
-/**
- * BarMeter Component
- * Reusable horizontal bar chart for showing metrics
- */
-export default function BarMeter({ label, value, max = 100, color = "#D85A30", suffix = "%" }) {
- const pct = Math.min((value / max) * 100, 100);
-
- return (
-
-
- {label}
-
- {value}{suffix}
-
-
-
-
- );
-}
diff --git a/frontend/policy-dashboards/src/components/shared/Compare.jsx b/frontend/policy-dashboards/src/components/shared/Compare.jsx
deleted file mode 100644
index 02244e17b5e7e02b8f33874e882359414a9d99f0..0000000000000000000000000000000000000000
--- a/frontend/policy-dashboards/src/components/shared/Compare.jsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import React from 'react';
-
-/**
- * Compare Component
- * Four-column comparison: This District → Republican → Democratic → National
- */
-export default function Compare({ benchmarks, metric = "value", prefix = "$", suffix = "" }) {
- const buckets = [
- { key: 'thisDistrict', color: '#D85A30' },
- { key: 'republicanAvg', color: '#BA7517' },
- { key: 'democraticAvg', color: '#185FA5' },
- { key: 'nationalAvg', color: '#1D9E75' }
- ];
-
- return (
-
- {buckets.map(bucket => {
- const data = benchmarks[bucket.key];
- const value = typeof data === 'object' ? data[metric] : data;
- const label = typeof data === 'object' ? data.label : bucket.key;
-
- return (
-
-
- {prefix}{value}{suffix}
-
-
- {label}
-
-
- );
- })}
-
- );
-}
diff --git a/frontend/policy-dashboards/src/components/shared/DashboardTile.jsx b/frontend/policy-dashboards/src/components/shared/DashboardTile.jsx
deleted file mode 100644
index a29eaf10f72506fbf4aa148848b668dcd9f41c09..0000000000000000000000000000000000000000
--- a/frontend/policy-dashboards/src/components/shared/DashboardTile.jsx
+++ /dev/null
@@ -1,171 +0,0 @@
-import React from 'react';
-import { ArrowRight, TrendingUp, Clock, DollarSign, Users } from 'lucide-react';
-
-/**
- * DashboardTile Component
- * Tile-based navigation for dashboards
- */
-export default function DashboardTile({
- id,
- title,
- metric,
- context,
- summary,
- discomfort,
- icon: Icon,
- onClick
-}) {
- const getDiscomfortColor = (score) => {
- if (score >= 9) return '#D85A30';
- if (score >= 7) return '#BA7517';
- return '#888';
- };
-
- return (
-
onClick(id)}
- style={{
- background: 'white',
- border: '1px solid #eee',
- borderRadius: 12,
- padding: 18,
- cursor: 'pointer',
- transition: 'all 0.2s ease',
- position: 'relative',
- overflow: 'hidden'
- }}
- onMouseEnter={(e) => {
- e.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,0.1)';
- e.currentTarget.style.transform = 'translateY(-2px)';
- }}
- onMouseLeave={(e) => {
- e.currentTarget.style.boxShadow = 'none';
- e.currentTarget.style.transform = 'translateY(0)';
- }}
- >
- {/* Discomfort indicator */}
-
-
- {/* Icon */}
- {Icon && (
-
-
-
- )}
-
- {/* Title */}
-
- {title}
-
-
- {/* Metrics */}
-
-
-
- {metric}
-
-
- {context}
-
-
-
-
- {/* Summary */}
-
- {summary}
-
-
- {/* Footer */}
-
-
- {discomfort >= 9 ? '⚠️ High Impact' : discomfort >= 7 ? '⚡ Medium Impact' : '📊 Analysis'}
-
-
-
-
- );
-}
-
-/**
- * DashboardGrid Component
- * Grid layout for dashboard tiles
- */
-export function DashboardGrid({ dashboards, onNavigate }) {
- const icons = [TrendingUp, Clock, DollarSign, Users];
-
- return (
-
- {dashboards.map((dashboard, i) => (
-
- ))}
-
- );
-}
diff --git a/frontend/policy-dashboards/src/components/shared/DecisionCard.jsx b/frontend/policy-dashboards/src/components/shared/DecisionCard.jsx
deleted file mode 100644
index e5eed0c69582e0ace3b61a9f162aa4f968a27d9f..0000000000000000000000000000000000000000
--- a/frontend/policy-dashboards/src/components/shared/DecisionCard.jsx
+++ /dev/null
@@ -1,253 +0,0 @@
-import React from 'react';
-import { Users, MessageSquare, FileText, Calendar, CheckCircle, XCircle } from 'lucide-react';
-
-/**
- * DecisionCard Component
- * Shows individual decision with speakers, rationale, and details
- */
-export default function DecisionCard({ decision, onClick }) {
- const {
- decision_summary,
- outcome,
- primary_rationale,
- supporters = [],
- opponents = [],
- vote_result,
- meeting_date,
- tradeoffs_discussed = [],
- evidence_cited = [],
- policy_domain = 'general',
- community_gap,
- ntee_code
- } = decision;
-
- const domainColors = {
- health: '#1D9E75',
- education: '#185FA5',
- facilities: '#BA7517',
- budget: '#D85A30',
- personnel: '#6B4C9A',
- safety: '#E24B4A',
- community: '#2C7A7B',
- policy: '#744210',
- general: '#888'
- };
-
- const isApproved = outcome?.toLowerCase().includes('approved') ||
- outcome?.toLowerCase().includes('passed');
-
- return (
-
{
- e.currentTarget.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)';
- e.currentTarget.style.borderLeftWidth = '6px';
- }}
- onMouseLeave={(e) => {
- e.currentTarget.style.boxShadow = 'none';
- e.currentTarget.style.borderLeftWidth = '4px';
- }}
- >
- {/* Header */}
-
-
-
- {decision_summary}
-
-
-
-
- {meeting_date ? new Date(meeting_date).toLocaleDateString() : 'Date unknown'}
-
- {vote_result && (
-
- Vote: {vote_result}
-
- )}
-
-
-
-
- {isApproved ? : }
- {outcome || 'Unknown'}
-
-
-
- {/* Rationale */}
- {primary_rationale && (
-
-
-
- Primary Rationale
-
-
- "{primary_rationale}"
-
-
- )}
-
- {/* Speakers */}
- {(supporters.length > 0 || opponents.length > 0) && (
-
- {supporters.length > 0 && (
-
-
-
- Supporters ({supporters.length})
-
- {supporters.slice(0, 2).map((supporter, i) => (
-
- • {typeof supporter === 'string' ? supporter : supporter.name || 'Unknown'}
- {supporter.role && ({supporter.role}) }
-
- ))}
- {supporters.length > 2 && (
-
- +{supporters.length - 2} more
-
- )}
-
- )}
-
- {opponents.length > 0 && (
-
-
-
- Opponents ({opponents.length})
-
- {opponents.slice(0, 2).map((opponent, i) => (
-
- • {typeof opponent === 'string' ? opponent : opponent.name || 'Unknown'}
- {opponent.role && ({opponent.role}) }
-
- ))}
- {opponents.length > 2 && (
-
- +{opponents.length - 2} more
-
- )}
-
- )}
-
- )}
-
- {/* Tradeoffs & Evidence */}
-
- {tradeoffs_discussed.length > 0 && (
-
- {tradeoffs_discussed.length} tradeoff{tradeoffs_discussed.length > 1 ? 's' : ''}
-
- )}
- {evidence_cited.length > 0 && (
-
-
- {evidence_cited.length} source{evidence_cited.length > 1 ? 's' : ''}
-
- )}
- {community_gap?.nonprofit_filling_gap && (
-
- 🤝 Community filling gap
-
- )}
- {ntee_code && (
-
- NTEE: {ntee_code}
-
- )}
-
-
- );
-}
diff --git a/frontend/policy-dashboards/src/components/shared/FilterPanel.jsx b/frontend/policy-dashboards/src/components/shared/FilterPanel.jsx
deleted file mode 100644
index 9ff1ebfbc6a8dc6018de9ea7b31d9cf03d9bbd42..0000000000000000000000000000000000000000
--- a/frontend/policy-dashboards/src/components/shared/FilterPanel.jsx
+++ /dev/null
@@ -1,240 +0,0 @@
-import React, { useState } from 'react';
-import { Search, Filter, X } from 'lucide-react';
-
-/**
- * FilterPanel Component
- * Allows filtering by policy domains/keywords and search
- */
-export default function FilterPanel({
- selectedDomains = [],
- onDomainToggle,
- searchQuery = "",
- onSearchChange,
- onClear
-}) {
- const [showFilters, setShowFilters] = useState(false);
-
- const policyDomains = [
- { id: 'health', label: 'Health & Wellness', color: '#1D9E75' },
- { id: 'education', label: 'Education & Curriculum', color: '#185FA5' },
- { id: 'facilities', label: 'Facilities & Infrastructure', color: '#BA7517' },
- { id: 'budget', label: 'Budget & Finance', color: '#D85A30' },
- { id: 'personnel', label: 'Personnel & Staffing', color: '#6B4C9A' },
- { id: 'safety', label: 'Safety & Security', color: '#E24B4A' },
- { id: 'community', label: 'Community & Partnerships', color: '#2C7A7B' },
- { id: 'policy', label: 'Policy & Governance', color: '#744210' }
- ];
-
- const keywords = [
- 'dental', 'health', 'screening', 'wellness', 'nurse',
- 'budget', 'funding', 'capital', 'expenditure',
- 'facility', 'building', 'construction', 'renovation',
- 'teacher', 'salary', 'contract', 'hiring',
- 'curriculum', 'textbook', 'program', 'academic',
- 'safety', 'security', 'police', 'emergency',
- 'community', 'partnership', 'grant', 'donation'
- ];
-
- const hasActiveFilters = selectedDomains.length > 0 || searchQuery.length > 0;
-
- return (
-
- {/* Search Bar */}
-
-
-
- onSearchChange(e.target.value)}
- style={{
- width: '100%',
- padding: '8px 12px 8px 36px',
- border: '1px solid #ddd',
- borderRadius: 8,
- fontSize: 13,
- fontFamily: 'inherit'
- }}
- />
- {searchQuery && (
- onSearchChange('')}
- style={{
- position: 'absolute',
- right: 8,
- top: '50%',
- transform: 'translateY(-50%)',
- background: 'none',
- border: 'none',
- cursor: 'pointer',
- color: '#999',
- padding: 4
- }}
- >
-
-
- )}
-
-
-
setShowFilters(!showFilters)}
- style={{
- padding: '8px 16px',
- border: '1px solid',
- borderColor: showFilters ? '#888' : '#ddd',
- borderRadius: 8,
- background: showFilters ? '#f5f5f2' : 'white',
- cursor: 'pointer',
- fontSize: 13,
- fontWeight: 500,
- display: 'flex',
- alignItems: 'center',
- gap: 6,
- color: showFilters ? '#222' : '#666'
- }}
- >
-
- Filters
- {selectedDomains.length > 0 && (
-
- {selectedDomains.length}
-
- )}
-
-
- {hasActiveFilters && (
-
- Clear All
-
- )}
-
-
- {/* Filter Panel */}
- {showFilters && (
-
-
-
- Policy Domains
-
-
- {policyDomains.map(domain => {
- const isSelected = selectedDomains.includes(domain.id);
- return (
- onDomainToggle(domain.id)}
- style={{
- padding: '6px 12px',
- borderRadius: 16,
- border: '1px solid',
- borderColor: isSelected ? domain.color : '#ddd',
- background: isSelected ? domain.color : 'white',
- color: isSelected ? 'white' : '#666',
- fontSize: 12,
- cursor: 'pointer',
- fontWeight: isSelected ? 500 : 400,
- transition: 'all 0.2s ease'
- }}
- >
- {domain.label}
-
- );
- })}
-
-
-
-
-
- Common Keywords
-
-
- {keywords.map(keyword => (
- onSearchChange(keyword)}
- style={{
- padding: '4px 10px',
- borderRadius: 12,
- border: '1px solid #ddd',
- background: searchQuery === keyword ? '#f0f0ee' : 'white',
- color: '#666',
- fontSize: 11,
- cursor: 'pointer'
- }}
- >
- {keyword}
-
- ))}
-
-
-
- )}
-
- );
-}
diff --git a/frontend/policy-dashboards/src/components/shared/InsightBox.jsx b/frontend/policy-dashboards/src/components/shared/InsightBox.jsx
deleted file mode 100644
index 336b1a97b6768218bf09c22012783856f5021321..0000000000000000000000000000000000000000
--- a/frontend/policy-dashboards/src/components/shared/InsightBox.jsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import React from 'react';
-
-/**
- * InsightBox Component
- * Bottom summary box with "The logic" explanation
- */
-export default function InsightBox({ title = "The logic", children, type = "default" }) {
- const styles = {
- default: {
- background: '#f5f5f2',
- borderLeft: 'none'
- },
- warning: {
- background: '#fff4e6',
- borderLeft: '3px solid #BA7517'
- },
- critical: {
- background: '#ffe6e6',
- borderLeft: '3px solid #D85A30'
- }
- };
-
- const style = styles[type] || styles.default;
-
- return (
-
- {title}: {children}
-
- );
-}
diff --git a/frontend/policy-dashboards/src/components/shared/MetricCard.jsx b/frontend/policy-dashboards/src/components/shared/MetricCard.jsx
deleted file mode 100644
index ec1304a791725eb9a1abbe438fac2a22123dbeeb..0000000000000000000000000000000000000000
--- a/frontend/policy-dashboards/src/components/shared/MetricCard.jsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import React from 'react';
-
-/**
- * MetricCard Component
- * Display key metrics with optional positive/negative/neutral tone
- */
-export default function MetricCard({ value, label, tone = "neutral" }) {
- const colors = {
- positive: "#1D9E75",
- negative: "#D85A30",
- neutral: "#222"
- };
-
- return (
-
-
- {value}
-
-
- {label}
-
-
- );
-}
diff --git a/frontend/policy-dashboards/src/data/dashboardData.js b/frontend/policy-dashboards/src/data/dashboardData.js
deleted file mode 100644
index 6cac2c4aa52e99da1bb05205d813e25b3dd797e6..0000000000000000000000000000000000000000
--- a/frontend/policy-dashboards/src/data/dashboardData.js
+++ /dev/null
@@ -1,321 +0,0 @@
-/**
- * Dashboard Data Configuration
- *
- * This file contains all the data for the accountability dashboards.
- * UPDATE THIS FILE with real data from your Python analysis output.
- *
- * Data source: output/tuscaloosa_accountability_dashboards.json
- */
-
-export const metadata = {
- title: "P.A.T.H. – Policy Accountability and Tracking Hub",
- description: "Track government decisions and discover community solutions",
- analysisDate: "2026-04-24",
- maxDiscomfortScore: 10
-};
-
-// ================================================================
-// DASHBOARD 1: Words vs. Dollars (Rhetoric Gap)
-// ================================================================
-export const rhetoricGapData = {
- // What they SAY
- sentimentScore: 92, // % positive sentiment
- totalMentions: 50,
- sampleQuotes: [
- "Student wellness is a top priority for this district",
- "We are deeply committed to the health and wellbeing of every child",
- "Social-emotional and physical health are foundational to learning"
- ],
-
- // What they FUND
- budgetCategory: "Contracted Health Services (Dental/Vision)",
- priorYearAmount: 320000,
- currentYearAmount: 200000,
- budgetDelta: -120000,
- budgetDeltaPercent: -37.5,
-
- // Context
- adminCostGrowth: 31, // % increase in admin costs same period
-
- // Benchmarks - UPDATE WITH REAL DATA
- benchmarks: {
- thisDistrict: {
- perStudent: 41,
- label: "This District"
- },
- republicanAvg: {
- perStudent: 74,
- label: "Republican Districts Avg"
- },
- democraticAvg: {
- perStudent: 98,
- label: "Democratic Districts Avg"
- },
- nationalAvg: {
- perStudent: 112,
- label: "National Average"
- }
- },
-
- // The Logic
- gapType: "Marketing Rationale",
- conclusion: "Verbal commitment to student health is high — fiscal priority is declining.",
- inference: "Words go up, dollars go down. Health funds are being used as a buffer for rising admin costs."
-};
-
-// ================================================================
-// DASHBOARD 2: Delayed 6 Months and Counting (Logic Chain)
-// ================================================================
-export const logicChainData = {
- topic: "West Alabama Community Dental Clinic Partnership",
-
- // Timeline
- firstMentioned: "2025-10-15",
- monthsInLimbo: 6,
- totalDeferrals: 4,
-
- // Justification history
- justifications: [
- {
- month: "Month 1 (Oct 2025)",
- status: "deferred",
- reason: "Waiting for state tax revenue projections",
- speaker: "Board Member Johnson"
- },
- {
- month: "Month 2 (Nov 2025)",
- status: "work session",
- reason: "Moved to subcommittee for further review",
- speaker: "Superintendent Smith"
- },
- {
- month: "Month 3 (Dec 2025)",
- status: "deferred",
- reason: "No quorum reached on agenda item",
- speaker: "Board Chair Davis"
- },
- {
- month: "Month 4 (Jan 2026)",
- status: "deferred",
- reason: "Waiting for legal clarity on liability",
- speaker: "Risk Manager Patricia Johnson"
- },
- {
- month: "Month 5-6 (Feb-Apr 2026)",
- status: "not scheduled",
- reason: "Election cycle approaching — not scheduled for vote",
- speaker: "N/A"
- }
- ],
-
- // Benchmarks
- benchmarks: {
- thisDistrict: {
- activePrograms: 0,
- label: "This District"
- },
- republicanAvg: {
- activePrograms: 14,
- label: "Republican States"
- },
- democraticAvg: {
- activePrograms: 21,
- label: "Democratic States"
- },
- nationalAvg: {
- activePrograms: 35,
- label: "States with Programs"
- }
- },
-
- // The Logic
- patternType: "Rationale of Attrition",
- conclusion: "Administrative 'analysis' is a strategic tool to avoid a final yes or no.",
- inference: "The board isn't debating merit — they're waiting for momentum to fade before the election cycle."
-};
-
-// ================================================================
-// DASHBOARD 3: What Got Funded Instead (Displacement Matrix)
-// ================================================================
-export const displacementData = {
- topic: "2026 Capital Project Prioritization",
-
- // The matrix
- displacements: [
- {
- winner: "Athletic Turf Replacement",
- winnerAmount: 850000,
- loser: "Fluoride Water System Upgrade",
- loserAmount: 0,
- tradeoffFactor: "Visibility — Turf is a PR win; fluoride is hidden"
- },
- {
- winner: "Admin Building HVAC System",
- winnerAmount: 2000000,
- loser: "Dental Screening Pilot Program",
- loserAmount: 0,
- tradeoffFactor: "Asset Maintenance — Building upkeep over health services"
- },
- {
- winner: "Police Vehicle Fleet Upgrade",
- winnerAmount: 450000,
- loser: "School Nurse Salary Supplements",
- loserAmount: 0,
- tradeoffFactor: "Public Safety — Police are a 'primary' rationale"
- }
- ],
-
- // Benchmarks - per student spending
- benchmarks: {
- thisDistrict: {
- healthCapital: 0,
- athleticCapital: 170,
- label: "This District"
- },
- republicanAvg: {
- healthCapital: 29,
- athleticCapital: 95,
- label: "Republican Districts"
- },
- democraticAvg: {
- healthCapital: 48,
- athleticCapital: 85,
- label: "Democratic Districts"
- },
- nationalAvg: {
- healthCapital: 42,
- athleticCapital: 88,
- label: "National Average"
- }
- },
-
- // The Logic
- priorityPattern: "Legacy Rationale",
- conclusion: "The Board prioritizes visible assets over invisible health infrastructure.",
- inference: "Officials fund ribbon-cuttings. The community trades dental health for political visibility."
-};
-
-// ================================================================
-// DASHBOARD 4: One Memo Beat 240 Residents (Influence Radar)
-// ================================================================
-export const influenceData = {
- topic: "School-Based Dental Screening Policy",
-
- // Influence actors
- actors: [
- {
- actor: "Risk / Legal memo (1 document)",
- influence: 92,
- type: "blocker",
- contactName: "Patricia Johnson, Risk Manager",
- documents: 1
- },
- {
- actor: "External consultant report",
- influence: 85,
- type: "blocker",
- contactName: "Education Finance Group LLC",
- documents: 1
- },
- {
- actor: "240+ citizen comments in favor",
- influence: 4,
- type: "public",
- contactName: "Public testimony",
- documents: 240
- }
- ],
-
- // Summary metrics
- publicComments: 240,
- publicSupportRatio: 98, // % in favor
- legalMemos: 1,
- consultantReports: 1,
-
- // Benchmarks - liability context
- benchmarks: {
- thisDistrict: {
- liabilitySuits: "Program Blocked",
- label: "This District"
- },
- republicanAvg: {
- liabilitySuits: 0,
- label: "Republican States"
- },
- democraticAvg: {
- liabilitySuits: 0,
- label: "Democratic States"
- },
- nationalAvg: {
- liabilitySuits: 0,
- label: "All States Combined"
- }
- },
-
- // The Logic
- powerStructure: "Technocratic Rationale",
- vetoHolder: "Patricia Johnson, Risk Manager",
- conclusion: "Internal risk management holds veto power that outweighs 100% of public input.",
- inference: "The district's lawyers and CFO are writing public health policy. One liability memo has 92% influence despite zero successful suits in any state with screening programs."
-};
-
-// ================================================================
-// SUMMARY PAGE DATA
-// ================================================================
-export const summaryData = {
- headline: "This isn't a left-vs-right debate. It's a pattern.",
- subheadline: "Four ways decision-making in Tuscaloosa diverges from both Republican and Democratic averages",
-
- findings: [
- {
- id: 1,
- title: "They cut health spending while praising wellness",
- metric: "$41/student",
- context: "vs. $112 national avg",
- discomfort: 9,
- summary: "92% positive sentiment about 'wellness' in meetings, but $120k budget cut to dental/vision services"
- },
- {
- id: 2,
- title: "Delayed 6 months and counting",
- metric: "4 deferrals",
- context: "4 different excuses",
- discomfort: 10,
- summary: "Dental clinic partnership has been 'under review' with shifting justifications since October 2025"
- },
- {
- id: 3,
- title: "What got funded instead",
- metric: "$850k turf",
- context: "vs. $0 dental screening",
- discomfort: 9,
- summary: "Visible projects (athletic fields, HVAC) prioritized over invisible health infrastructure"
- },
- {
- id: 4,
- title: "One memo beat 240 residents",
- metric: "92% influence",
- context: "from 1 risk manager",
- discomfort: 10,
- summary: "Patricia Johnson's liability memo had 23x more influence than 240 citizen testimonies"
- }
- ],
-
- howToUse: {
- title: "How to use this in the room",
- strategies: [
- {
- dont: "Argue the 'need'",
- do: "Show the rhetoric gap — they already agree health matters (50 mentions, 92% positive)"
- },
- {
- dont: "Accept 'budget constraints'",
- do: "Show the displacement — they funded $850k for turf in the same budget cycle"
- },
- {
- dont: "Let them hide behind 'the board decided'",
- do: "Name the veto holder — Patricia Johnson's memo had 92% influence vs. 4% for public input"
- }
- ]
- }
-};
diff --git a/frontend/policy-dashboards/src/index.css b/frontend/policy-dashboards/src/index.css
deleted file mode 100644
index 1e557101e2f71e32c19e3f1e0fb8047843ac4c2b..0000000000000000000000000000000000000000
--- a/frontend/policy-dashboards/src/index.css
+++ /dev/null
@@ -1,31 +0,0 @@
-body {
- margin: 0;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
- 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
- sans-serif;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
- font-size: 16px;
- line-height: 1.6;
-}
-
-html {
- overflow-x: hidden;
-}
-
-code {
- font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
- monospace;
-}
-
-* {
- box-sizing: border-box;
-}
-
-button {
- font-family: inherit;
-}
-
-input {
- font-family: inherit;
-}
diff --git a/frontend/policy-dashboards/src/index.js b/frontend/policy-dashboards/src/index.js
deleted file mode 100644
index 2cb1087e76eb7b33e49ace3622c9e108cd1ed64c..0000000000000000000000000000000000000000
--- a/frontend/policy-dashboards/src/index.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom/client';
-import './index.css';
-import App from './App';
-
-const root = ReactDOM.createRoot(document.getElementById('root'));
-root.render(
-
-
-
-);
diff --git a/frontend/public/communityone_logo.jpg b/frontend/public/communityone_logo.jpg
deleted file mode 100644
index 67106acec272efff0b126b734c4d83681c2e18cf..0000000000000000000000000000000000000000
Binary files a/frontend/public/communityone_logo.jpg and /dev/null differ
diff --git a/frontend/public/communityone_logo.png b/frontend/public/communityone_logo.png
deleted file mode 100644
index 028d6be559d8015e84a2dbb407c1f223b90f2fde..0000000000000000000000000000000000000000
Binary files a/frontend/public/communityone_logo.png and /dev/null differ
diff --git a/frontend/public/communityone_logo.svg b/frontend/public/communityone_logo.svg
deleted file mode 100644
index ce4f291e644305123f831ff0b98c503d5342e3cf..0000000000000000000000000000000000000000
--- a/frontend/public/communityone_logo.svg
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- C1
-
-
-
-
diff --git a/frontend/public/communityone_logo_64.png b/frontend/public/communityone_logo_64.png
deleted file mode 100644
index 696b25f394add52200236e0c000c52be2ebdf0dd..0000000000000000000000000000000000000000
Binary files a/frontend/public/communityone_logo_64.png and /dev/null differ
diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico
deleted file mode 100644
index 0aad19ae960e9e7bbfa315c0ba4968b7400d1e18..0000000000000000000000000000000000000000
Binary files a/frontend/public/favicon.ico and /dev/null differ
diff --git a/frontend/public/privacyfacebook.html b/frontend/public/privacyfacebook.html
deleted file mode 100644
index ddb03823401c797c205ab76e2464bce99d432792..0000000000000000000000000000000000000000
--- a/frontend/public/privacyfacebook.html
+++ /dev/null
@@ -1,276 +0,0 @@
-
-
-
-
-
-
Privacy Policy - Open Navigator for Engagement
-
-
-
-
-
-
-
- Open Navigator for Engagement ("we," "our," or "us") is committed to protecting your privacy. This Privacy Policy explains how we collect, use, disclose, and safeguard your information when you use our platform.
-
-
-
- 1. Information We Collect
-
- 1.1 Information You Provide
- When you create an account or use our services, we may collect:
-
- Account Information: Email address, name, and profile information
- OAuth Provider Data: When you log in via Google, Facebook, GitHub, or HuggingFace, we receive your public profile information and email address
- User Preferences: Settings and preferences you configure within the application
-
-
- 1.2 Automatically Collected Information
-
- Usage Data: Pages visited, features used, and interactions with the platform
- Device Information: Browser type, operating system, IP address
- Cookies: We use essential cookies for authentication and session management
-
-
-
-
- 2. How We Use Your Information
- We use the collected information to:
-
- Provide, maintain, and improve our services
- Authenticate your account and maintain security
- Personalize your experience on the platform
- Send important updates about the service
- Analyze usage patterns to improve functionality
- Comply with legal obligations
-
-
-
-
- 3. Third-Party Authentication
-
-
OAuth Providers: We support login via Google, Facebook, GitHub, and HuggingFace. When you use these services:
-
- We only request access to your email address and basic profile information
- We do not store your social media passwords
- We do not post to your social media accounts
- You can revoke our access at any time through your provider's settings
-
-
-
-
-
- 4. Data Storage and Security
- We implement appropriate technical and organizational measures to protect your information:
-
- Encryption: Data is encrypted in transit using HTTPS/TLS
- Access Controls: Strict access controls limit who can access your data
- Secure Authentication: JWT tokens with secure secret keys
- Regular Updates: We keep our systems updated with security patches
-
- However, no method of transmission over the internet is 100% secure, and we cannot guarantee absolute security.
-
-
-
- 5. Data Sharing and Disclosure
- We do not sell your personal information. We may share your information only in the following circumstances:
-
- With Your Consent: When you explicitly authorize us to share information
- Service Providers: With trusted third-party services that help us operate (e.g., hosting providers)
- Legal Requirements: When required by law, court order, or governmental authority
- Business Transfers: In connection with a merger, acquisition, or sale of assets
-
-
-
-
- 6. Public Data Sources
- Our platform aggregates publicly available information from:
-
- City council meeting minutes and transcripts
- Government public records and budgets
- Nonprofit organization databases (IRS Form 990 data)
- Legislative information from state and local governments
-
- This public information is not considered personal data and is used to provide civic engagement insights.
-
-
-
- 7. Your Rights and Choices
- You have the following rights regarding your personal information:
-
- Access: Request a copy of the personal information we hold about you
- Correction: Request correction of inaccurate information
- Deletion: Request deletion of your account and personal data
- Data Portability: Request your data in a portable format
- Opt-Out: Unsubscribe from non-essential communications
-
- To exercise these rights, contact us at the email address provided below.
-
-
-
- 8. Children's Privacy
- Our service is not intended for children under 13 years of age. We do not knowingly collect personal information from children under 13. If you become aware that a child has provided us with personal information, please contact us, and we will delete such information.
-
-
-
- 9. Data Retention
- We retain your personal information for as long as necessary to:
-
- Provide our services to you
- Comply with legal obligations
- Resolve disputes and enforce our agreements
-
- When you delete your account, we will delete or anonymize your personal information within 30 days, except where required to retain it by law.
-
-
-
- 10. International Data Transfers
- Your information may be transferred to and processed in countries other than your country of residence. We ensure appropriate safeguards are in place to protect your information in accordance with this Privacy Policy.
-
-
-
- 11. Changes to This Privacy Policy
- We may update this Privacy Policy from time to time. We will notify you of any changes by:
-
- Posting the new Privacy Policy on this page
- Updating the "Last Updated" date
- Sending you an email notification (for material changes)
-
- Your continued use of our services after changes constitutes acceptance of the updated policy.
-
-
-
- 12. Contact Us
- If you have questions about this Privacy Policy or our privacy practices, please contact us:
-
-
-
-
- 13. Additional Information for EU/UK Users (GDPR)
- If you are located in the European Union or United Kingdom, you have additional rights under GDPR:
-
- Legal Basis: We process your data based on consent, contract performance, and legitimate interests
- Data Protection Officer: You may contact our DPO at privacy@communityone.com
- Supervisory Authority: You have the right to lodge a complaint with your local data protection authority
- Automated Decision-Making: We do not use automated decision-making or profiling that produces legal effects
-
-
-
-
- 14. California Privacy Rights (CCPA)
- If you are a California resident, you have specific rights under the California Consumer Privacy Act (CCPA):
-
- Right to Know: What personal information we collect, use, and share
- Right to Delete: Request deletion of your personal information
- Right to Opt-Out: Opt-out of the sale of personal information (we do not sell your data)
- Non-Discrimination: We will not discriminate against you for exercising your rights
-
-
-
-
- © 2026 Community One. All rights reserved.
- Open Navigator for Engagement is an open-source project licensed under the MIT License.
- Return to Home | View on GitHub
-
-
-
-
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
deleted file mode 100644
index c4e1168580d5b54b6caab0c09aa22da9815a0def..0000000000000000000000000000000000000000
--- a/frontend/src/App.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import { Routes, Route } from 'react-router-dom'
-import Layout from './components/Layout'
-import Home from './pages/Home'
-import HomeModern from './pages/HomeModern'
-import Dashboard from './pages/Dashboard'
-import Analytics from './pages/Analytics'
-import Heatmap from './pages/Heatmap'
-import Documents from './pages/Documents'
-import Opportunities from './pages/Opportunities'
-import Nonprofits from './pages/Nonprofits'
-import NonprofitsHF from './pages/NonprofitsHF'
-import Settings from './pages/Settings'
-import PeopleFinder from './pages/PeopleFinder'
-import DebateFinder from './pages/DebateGrader'
-import Profile from './pages/Profile'
-import Explore from './pages/Explore'
-import Events from './pages/Events'
-import Services from './pages/Services'
-import Developers from './pages/Developers'
-import Hackathons from './pages/Hackathons'
-import OpenSource from './pages/OpenSource'
-import AdvocacyTopics from './pages/AdvocacyTopics'
-import FactChecking from './pages/FactChecking'
-import UnifiedSearch from './pages/UnifiedSearch'
-import JurisdictionsSearch from './pages/JurisdictionsSearch'
-
-function App() {
- return (
-
- {/* Modern home page without Layout (has its own header) */}
- } />
-
- {/* Classic home page (if needed) */}
- }>
- } />
-
-
- {/* All other pages with sidebar layout */}
- }>
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
-
-
- )
-}
-
-export default App
diff --git a/frontend/src/components/AddressLookup.tsx b/frontend/src/components/AddressLookup.tsx
deleted file mode 100644
index f143b7af23dfbf8ff0a5501923a2160c83838409..0000000000000000000000000000000000000000
--- a/frontend/src/components/AddressLookup.tsx
+++ /dev/null
@@ -1,671 +0,0 @@
-import { useState, useEffect, useRef } from 'react'
-import { MapPinIcon, MagnifyingGlassIcon } from '@heroicons/react/24/outline'
-import { stateNameToCode } from '../utils/stateMapping'
-import { useLocation as useLocationContext } from '../contexts/LocationContext'
-
-interface LocationData {
- address: string
- state: string
- county: string
- city: string
- latitude?: number
- longitude?: number
-}
-
-interface AddressLookupProps {
- onLocationFound: (location: LocationData) => void
- initialAddress?: string
- compact?: boolean
-}
-
-export default function AddressLookup({ onLocationFound, initialAddress = '', compact = false }: AddressLookupProps) {
- const { clearLocation } = useLocationContext()
- const [address, setAddress] = useState(initialAddress)
- const [isLoading, setIsLoading] = useState(false)
- const [error, setError] = useState
(null)
- const [suggestions, setSuggestions] = useState([])
- const [foundLocation, setFoundLocation] = useState(null)
- const [showSuggestions, setShowSuggestions] = useState(false)
- const [selectedIndex, setSelectedIndex] = useState(-1)
- const debounceTimer = useRef(null)
- const inputRef = useRef(null)
-
- // Fetch suggestions as user types
- const fetchSuggestions = async (query: string) => {
- if (query.trim().length < 3) {
- setSuggestions([])
- setShowSuggestions(false)
- return
- }
-
- try {
- const response = await fetch(
- `https://nominatim.openstreetmap.org/search?` +
- `q=${encodeURIComponent(query)}&` +
- `format=json&` +
- `addressdetails=1&` +
- `countrycodes=us&` +
- `limit=5`,
- {
- headers: {
- 'User-Agent': 'CommunityOne-Navigator/1.0'
- }
- }
- )
-
- if (!response.ok) {
- return
- }
-
- const data = await response.json()
-
- // Deduplicate results using OSM unique IDs
- const uniqueResults = data.reduce((acc: any[], current: any) => {
- const osmKey = `${current.osm_type}_${current.osm_id}`
- const exists = acc.some((item) => {
- const itemKey = `${item.osm_type}_${item.osm_id}`
- return itemKey === osmKey
- })
- if (!exists) {
- acc.push(current)
- }
- return acc
- }, [])
-
- setSuggestions(uniqueResults)
- setShowSuggestions(uniqueResults.length > 0)
- setSelectedIndex(-1)
- } catch (err) {
- console.error('Autocomplete error:', err)
- }
- }
-
- // Handle address input change with debouncing
- const handleAddressChange = (value: string) => {
- setAddress(value)
- setError(null)
-
- // Clear previous timer
- if (debounceTimer.current) {
- clearTimeout(debounceTimer.current)
- }
-
- // Set new timer
- debounceTimer.current = setTimeout(() => {
- fetchSuggestions(value)
- }, 300)
- }
-
- // Cleanup timer on unmount
- useEffect(() => {
- return () => {
- if (debounceTimer.current) {
- clearTimeout(debounceTimer.current)
- }
- }
- }, [])
-
- const lookupAddress = async (addressToLookup: string) => {
- if (!addressToLookup.trim()) {
- setError('Please enter an address')
- return
- }
-
- setIsLoading(true)
- setError(null)
- setSuggestions([])
- setShowSuggestions(false)
-
- try {
- // Use Nominatim (OpenStreetMap) geocoding service
- const response = await fetch(
- `https://nominatim.openstreetmap.org/search?` +
- `q=${encodeURIComponent(addressToLookup)}&` +
- `format=json&` +
- `addressdetails=1&` +
- `countrycodes=us&` +
- `limit=5`,
- {
- headers: {
- 'User-Agent': 'CommunityOne-Navigator/1.0'
- }
- }
- )
-
- if (!response.ok) {
- throw new Error('Failed to lookup address')
- }
-
- const data = await response.json()
-
- if (data.length === 0) {
- setError('Address not found. Please try a different address or be more specific.')
- return
- }
-
- // Deduplicate results using OSM unique IDs
- const uniqueResults = data.reduce((acc: any[], current: any) => {
- // Use OSM type + ID as unique key (most reliable)
- const osmKey = `${current.osm_type}_${current.osm_id}`
-
- const exists = acc.some((item) => {
- const itemKey = `${item.osm_type}_${item.osm_id}`
- return itemKey === osmKey
- })
-
- if (!exists) {
- acc.push(current)
- }
- return acc
- }, [])
-
- // If we have multiple unique results, show suggestions
- if (uniqueResults.length > 1) {
- setSuggestions(uniqueResults)
- setShowSuggestions(true)
- return
- }
-
- // Single result - process it
- processResult(uniqueResults[0])
- } catch (err) {
- console.error('Address lookup error:', err)
- setError('Failed to lookup address. Please try again.')
- } finally {
- setIsLoading(false)
- }
- }
-
- const processResult = (result: any) => {
- const addr = result.address
-
- // Convert state name to 2-letter code
- const stateName = addr.state || ''
- const stateCode = stateNameToCode(stateName)
- console.log(`🗺️ [AddressLookup] State conversion: "${stateName}" → "${stateCode}"`)
-
- const locationData: LocationData = {
- address: result.display_name,
- state: stateCode,
- county: addr.county || '',
- city: addr.city || addr.town || addr.village || addr.municipality || '',
- latitude: parseFloat(result.lat),
- longitude: parseFloat(result.lon),
- }
-
- // Validate we got the essential data
- if (!locationData.state || !locationData.city) {
- setError('Could not determine city and state from this address. Please be more specific.')
- setSuggestions([])
- setShowSuggestions(false)
- return
- }
-
- console.log('📍 [AddressLookup] Location found:', locationData)
- setSuggestions([])
- setShowSuggestions(false)
- setFoundLocation(locationData)
- onLocationFound(locationData)
- }
-
- const handleSubmit = (e: React.FormEvent) => {
- e.preventDefault()
-
- // If a suggestion is selected, use that
- if (selectedIndex >= 0 && suggestions[selectedIndex]) {
- processResult(suggestions[selectedIndex])
- } else {
- lookupAddress(address)
- }
- }
-
- const handleSuggestionClick = (suggestion: any) => {
- setAddress(suggestion.display_name)
- processResult(suggestion)
- }
-
- const handleKeyDown = (e: React.KeyboardEvent) => {
- if (!showSuggestions || suggestions.length === 0) return
-
- switch (e.key) {
- case 'ArrowDown':
- e.preventDefault()
- setSelectedIndex(prev =>
- prev < suggestions.length - 1 ? prev + 1 : prev
- )
- break
- case 'ArrowUp':
- e.preventDefault()
- setSelectedIndex(prev => prev > 0 ? prev - 1 : -1)
- break
- case 'Enter':
- if (selectedIndex >= 0) {
- e.preventDefault()
- processResult(suggestions[selectedIndex])
- }
- break
- case 'Escape':
- setShowSuggestions(false)
- setSelectedIndex(-1)
- break
- }
- }
-
- const useMyLocation = () => {
- if (!navigator.geolocation) {
- setError('Geolocation is not supported by your browser')
- return
- }
-
- setIsLoading(true)
- setError(null)
- setSuggestions([])
-
- navigator.geolocation.getCurrentPosition(
- async (position) => {
- const { latitude, longitude } = position.coords
-
- try {
- // Reverse geocode using Nominatim
- const response = await fetch(
- `https://nominatim.openstreetmap.org/reverse?` +
- `lat=${latitude}&` +
- `lon=${longitude}&` +
- `format=json&` +
- `addressdetails=1`,
- {
- headers: {
- 'User-Agent': 'CommunityOne-Navigator/1.0'
- }
- }
- )
-
- if (!response.ok) {
- throw new Error('Failed to reverse geocode location')
- }
-
- const data = await response.json()
-
- // Update the address input field
- setAddress(data.display_name)
-
- // Process the result
- processResult(data)
- } catch (err) {
- console.error('Reverse geocoding error:', err)
- setError('Failed to determine your location. Please enter your address manually.')
- } finally {
- setIsLoading(false)
- }
- },
- (error) => {
- console.error('Geolocation error:', error)
- setIsLoading(false)
-
- switch (error.code) {
- case error.PERMISSION_DENIED:
- setError('Location access denied. Please enter your address manually or enable location permissions.')
- break
- case error.POSITION_UNAVAILABLE:
- setError('Location information unavailable. Please enter your address manually.')
- break
- case error.TIMEOUT:
- setError('Location request timed out. Please try again or enter your address manually.')
- break
- default:
- setError('An error occurred while getting your location. Please enter your address manually.')
- }
- },
- {
- enableHighAccuracy: false, // Use fast network-based location instead of GPS
- timeout: 5000, // Reduced timeout since network location is faster
- maximumAge: 30000 // Allow 30s cached location for faster response
- }
- )
- }
-
- if (compact) {
- return (
-
- )
- }
-
- return (
-
-
-
- {/* Error Message */}
- {error && (
-
- )}
-
- {/* Note: Suggestions now appear as autocomplete dropdown above */}
-
- {/* Location Results */}
- {foundLocation && !compact && (
-
-
-
-
- Your Local Community
-
- window.location.href = '/'}
- className="text-sm text-white hover:text-primary-100 underline font-medium"
- >
- ← Back to Home
-
-
-
-
- Select a jurisdiction level below to explore organizations, meeting minutes, and contacts:
-
-
- {/* City */}
- {foundLocation.city && (
-
{
- window.location.href = `/?scope=city`
- }}
- className="bg-white rounded-lg p-4 shadow-sm hover:shadow-md hover:border-2 hover:border-blue-500 transition-all text-left w-full group"
- >
-
-
-
-
City
-
{foundLocation.city}
-
City Council
-
- Click to explore →
-
-
-
-
- )}
-
- {/* County */}
- {foundLocation.county && (
-
{
- window.location.href = `/?scope=county`
- }}
- className="bg-white rounded-lg p-4 shadow-sm hover:shadow-md hover:border-2 hover:border-green-500 transition-all text-left w-full group"
- >
-
-
-
-
County
-
{foundLocation.county}
-
County Board
-
- Click to explore →
-
-
-
-
- )}
-
- {/* State */}
- {foundLocation.state && (
-
{
- window.location.href = `/?scope=state`
- }}
- className="bg-white rounded-lg p-4 shadow-sm hover:shadow-md hover:border-2 hover:border-purple-500 transition-all text-left w-full group"
- >
-
-
-
-
State
-
{foundLocation.state}
-
State Legislature
-
- Click to explore →
-
-
-
-
- )}
-
- {/* School District */}
- {foundLocation.city && (
-
{
- window.location.href = `/?scope=community`
- }}
- className="bg-white rounded-lg p-4 shadow-sm hover:shadow-md hover:border-2 hover:border-amber-500 transition-all text-left w-full group"
- >
-
-
-
-
School District
-
{foundLocation.city} Unified
-
School Board
-
- Click to explore →
-
-
-
-
- )}
-
-
- {/* Action Buttons */}
-
-
Quick access to all local resources:
-
- {
- window.location.href = `/documents?state=${foundLocation.state}&city=${foundLocation.city}`
- }}
- className="flex-1 min-w-[200px] px-4 py-2 bg-white border-2 border-primary-600 text-primary-700 rounded-lg hover:bg-primary-50 transition-colors font-medium"
- >
- 📄 All Meeting Minutes
-
- {
- window.location.href = `/nonprofits?state=${foundLocation.state}&city=${foundLocation.city}`
- }}
- className="flex-1 min-w-[200px] px-4 py-2 bg-white border-2 border-primary-600 text-primary-700 rounded-lg hover:bg-primary-50 transition-colors font-medium"
- >
- 🏢 All Local Organizations
-
-
-
-
- {/* Start Over */}
-
- {
- setFoundLocation(null)
- setAddress('')
- setError(null)
- clearLocation() // Clear the global location context
- }}
- className="text-sm text-primary-600 hover:text-primary-700 font-medium underline"
- >
- Search Different Address
-
-
-
-
- )}
-
- )
-}
diff --git a/frontend/src/components/FollowButton.tsx b/frontend/src/components/FollowButton.tsx
deleted file mode 100644
index c76549482a1054f5e69d90e613cf3d3801d0e814..0000000000000000000000000000000000000000
--- a/frontend/src/components/FollowButton.tsx
+++ /dev/null
@@ -1,160 +0,0 @@
-import { useState } from 'react'
-import { useMutation, useQueryClient } from '@tanstack/react-query'
-import axios from 'axios'
-import { UserPlusIcon, UserMinusIcon } from '@heroicons/react/24/outline'
-import { CheckIcon } from '@heroicons/react/24/solid'
-
-interface FollowButtonProps {
- type: 'user' | 'leader' | 'organization' | 'cause'
- id: number
- initialFollowing?: boolean
- initialCount?: number
- showCount?: boolean
- compact?: boolean
- onFollowChange?: (following: boolean, count: number) => void
-}
-
-export default function FollowButton({
- type,
- id,
- initialFollowing = false,
- initialCount = 0,
- showCount = true,
- compact = false,
- onFollowChange
-}: FollowButtonProps) {
- const [isFollowing, setIsFollowing] = useState(initialFollowing)
- const [followerCount, setFollowerCount] = useState(initialCount)
- const [isHovered, setIsHovered] = useState(false)
- const queryClient = useQueryClient()
-
- const followMutation = useMutation({
- mutationFn: async () => {
- const endpoint = `/api/social/follow/${type}/${id}`
- const response = await axios.post(endpoint)
- return response.data
- },
- onSuccess: (data) => {
- setIsFollowing(true)
- setFollowerCount(data.follower_count)
- onFollowChange?.(true, data.follower_count)
- // Invalidate relevant queries
- queryClient.invalidateQueries({ queryKey: ['social', 'stats'] })
- queryClient.invalidateQueries({ queryKey: ['following', type] })
- }
- })
-
- const unfollowMutation = useMutation({
- mutationFn: async () => {
- const endpoint = `/api/social/follow/${type}/${id}`
- const response = await axios.delete(endpoint)
- return response.data
- },
- onSuccess: (data) => {
- setIsFollowing(false)
- setFollowerCount(data.follower_count)
- onFollowChange?.(false, data.follower_count)
- // Invalidate relevant queries
- queryClient.invalidateQueries({ queryKey: ['social', 'stats'] })
- queryClient.invalidateQueries({ queryKey: ['following', type] })
- }
- })
-
- const handleClick = () => {
- if (isFollowing) {
- unfollowMutation.mutate()
- } else {
- followMutation.mutate()
- }
- }
-
- const isLoading = followMutation.isPending || unfollowMutation.isPending
-
- // LinkedIn/Facebook style button
- if (compact) {
- return (
- setIsHovered(true)}
- onMouseLeave={() => setIsHovered(false)}
- className={`
- inline-flex items-center gap-1.5 px-4 py-1.5 rounded-full text-sm font-medium
- transition-all duration-200 border
- ${isFollowing
- ? isHovered
- ? 'bg-red-50 border-red-300 text-red-700 hover:bg-red-100'
- : 'bg-white border-gray-300 text-gray-700 hover:bg-gray-50'
- : 'bg-blue-600 border-blue-600 text-white hover:bg-blue-700'
- }
- disabled:opacity-50 disabled:cursor-not-allowed
- `}
- >
- {isLoading ? (
-
- ) : isFollowing ? (
- <>
- {isHovered ? (
-
- ) : (
-
- )}
- {isHovered ? 'Unfollow' : 'Following'}
- >
- ) : (
- <>
-
- Follow
- >
- )}
-
- )
- }
-
- // Full button with count
- return (
-
- setIsHovered(true)}
- onMouseLeave={() => setIsHovered(false)}
- className={`
- inline-flex items-center gap-2 px-5 py-2 rounded-lg text-sm font-semibold
- transition-all duration-200 border-2
- ${isFollowing
- ? isHovered
- ? 'bg-red-50 border-red-400 text-red-700 hover:bg-red-100'
- : 'bg-white border-gray-300 text-gray-700 hover:border-gray-400'
- : 'bg-blue-600 border-blue-600 text-white hover:bg-blue-700'
- }
- disabled:opacity-50 disabled:cursor-not-allowed shadow-sm hover:shadow-md
- `}
- >
- {isLoading ? (
-
- ) : isFollowing ? (
- <>
- {isHovered ? (
-
- ) : (
-
- )}
- {isHovered ? 'Unfollow' : 'Following'}
- >
- ) : (
- <>
-
- Follow
- >
- )}
-
-
- {showCount && (
-
- {followerCount.toLocaleString()} {followerCount === 1 ? 'follower' : 'followers'}
-
- )}
-
- )
-}
diff --git a/frontend/src/components/JurisdictionDiscovery.tsx b/frontend/src/components/JurisdictionDiscovery.tsx
deleted file mode 100644
index 1d23454dc134702552b77cd589a8c365f9782bc4..0000000000000000000000000000000000000000
--- a/frontend/src/components/JurisdictionDiscovery.tsx
+++ /dev/null
@@ -1,268 +0,0 @@
-import { useState } from 'react'
-import {
- ChevronDownIcon,
- ChevronUpIcon,
- CheckCircleIcon,
- GlobeAltIcon,
- VideoCameraIcon,
- DocumentTextIcon,
- ShareIcon
-} from '@heroicons/react/24/outline'
-
-interface JurisdictionDiscoveryProps {
- jurisdiction: {
- name: string
- state: string
- website?: string
- youtube_channels?: string[]
- facebook?: string
- twitter?: string
- agenda_portal?: string
- meeting_platform?: string
- completeness: number
- }
-}
-
-export default function JurisdictionDiscovery({ jurisdiction }: JurisdictionDiscoveryProps) {
- const [isExpanded, setIsExpanded] = useState(false)
-
- const hasData = jurisdiction.website || jurisdiction.youtube_channels?.length || jurisdiction.facebook
-
- return (
-
- {/* Header - Always Visible */}
-
-
-
-
-
-
- {jurisdiction.name}, {jurisdiction.state} - DISCOVERY COMPLETE!
-
-
-
- {/* Summary Stats */}
-
- {jurisdiction.website && (
-
-
- Website
-
- )}
- {jurisdiction.youtube_channels && jurisdiction.youtube_channels.length > 0 && (
-
-
- {jurisdiction.youtube_channels.length} YouTube Channel{jurisdiction.youtube_channels.length > 1 ? 's' : ''}
-
- )}
- {jurisdiction.agenda_portal && (
-
-
- Agenda Portal
-
- )}
- {(jurisdiction.facebook || jurisdiction.twitter) && (
-
-
- Social Media
-
- )}
-
-
- {/* Completeness Bar */}
- {hasData && (
-
-
-
-
- {jurisdiction.completeness}%
-
-
-
- Completeness: ~{Math.round(jurisdiction.completeness)}% - {
- jurisdiction.completeness >= 75 ? 'Good' :
- jurisdiction.completeness >= 50 ? 'Fair' :
- 'Limited'
- } digital infrastructure!
-
-
- )}
-
-
- {/* Expand/Collapse Button */}
- {hasData && (
-
setIsExpanded(!isExpanded)}
- className="ml-4 p-2 hover:bg-gray-100 rounded-lg transition-colors"
- >
- {isExpanded ? (
-
- ) : (
-
- )}
-
- )}
-
-
-
- {/* Expandable Details */}
- {isExpanded && hasData && (
-
-
- 🎯 {jurisdiction.name.toUpperCase()}, {jurisdiction.state} FINDINGS
-
-
-
- {/* Website */}
- {jurisdiction.website && (
-
- )}
-
- {/* Agenda Portal */}
- {jurisdiction.agenda_portal && (
-
- )}
-
- {/* YouTube Channels */}
- {jurisdiction.youtube_channels && jurisdiction.youtube_channels.length > 0 && (
-
-
📺 YouTube Channels:
- {jurisdiction.youtube_channels.map((channel, idx) => (
-
- ✅ @{channel}
-
- ))}
-
- )}
-
- {/* Social Media */}
- {(jurisdiction.facebook || jurisdiction.twitter) && (
-
-
📱 Social Media:
-
- {jurisdiction.facebook && (
-
- ✅ Facebook: {jurisdiction.facebook}
-
- )}
- {jurisdiction.twitter && (
-
- ✅ Twitter: {jurisdiction.twitter}
-
- )}
-
-
- )}
-
- {/* Meeting Platform */}
- {jurisdiction.meeting_platform && (
-
-
🏛️ Meeting Platform:
-
- {jurisdiction.meeting_platform}
-
-
- )}
-
- {/* Summary Table */}
-
-
📊 {jurisdiction.name.toUpperCase()} SUMMARY
-
-
-
- Category
- Found
- Details
-
-
-
-
- Website
- {jurisdiction.website ? '✅' : '❌'}
-
- {jurisdiction.website ? new URL(jurisdiction.website).hostname : 'Not found'}
-
-
-
- YouTube
- {jurisdiction.youtube_channels?.length ? '✅' : '❌'}
-
- {jurisdiction.youtube_channels?.length || 0} channel{jurisdiction.youtube_channels?.length !== 1 ? 's' : ''}
-
-
-
- Agendas
- {jurisdiction.agenda_portal ? '✅' : '❌'}
-
- {jurisdiction.agenda_portal ? 'Portal found' : 'Not available'}
-
-
-
- Social
- {jurisdiction.facebook || jurisdiction.twitter ? '✅' : '❌'}
-
- {[jurisdiction.facebook && 'Facebook', jurisdiction.twitter && 'Twitter'].filter(Boolean).join(', ') || 'None'}
-
-
-
- Platform
- {jurisdiction.meeting_platform ? '✅' : '❌'}
-
- {jurisdiction.meeting_platform || 'Unknown'}
-
-
-
-
-
-
- {/* Key Takeaway */}
-
-
💡 KEY TAKEAWAY
-
- The automation successfully discovered:
-
-
- {jurisdiction.website && ✅ Official website (automatic) }
- {(jurisdiction.youtube_channels?.length ?? 0) > 0 && ✅ YouTube channels (automatic) }
- {jurisdiction.agenda_portal && ✅ Agenda portal (found via link scanning) }
- {(jurisdiction.facebook || jurisdiction.twitter) && ✅ Social media (automatic) }
-
-
-
-
- )}
-
- {/* No Data Message */}
- {!hasData && (
-
-
No discovery data available yet. Run discovery pipeline to populate.
-
- )}
-
- )
-}
diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx
deleted file mode 100644
index 657c16a7fd32e2836bdeb1c38fab517b15e04f61..0000000000000000000000000000000000000000
--- a/frontend/src/components/Layout.tsx
+++ /dev/null
@@ -1,498 +0,0 @@
-import { Outlet, Link, useLocation, useNavigate } from 'react-router-dom'
-import { useState, Fragment } from 'react'
-import { Menu, Transition } from '@headlessui/react'
-import {
- HomeIcon,
- MapIcon,
- DocumentTextIcon,
- BellAlertIcon,
- BuildingLibraryIcon,
- Cog6ToothIcon,
- ChartBarIcon,
- MagnifyingGlassIcon,
- BookOpenIcon,
- UserGroupIcon,
- AcademicCapIcon,
- Bars3Icon,
- XMarkIcon,
- UserCircleIcon,
- ArrowRightOnRectangleIcon,
- ChevronDownIcon,
- MapPinIcon,
- HeartIcon,
- CodeBracketIcon,
-} from '@heroicons/react/24/outline'
-import { useAuth } from '../contexts/AuthContext'
-import { useLocation as useLocationContext } from '../contexts/LocationContext'
-
-const navigation = [
- { name: 'Home', href: '/', icon: HomeIcon },
- { name: 'Explore Data', href: '/explore', icon: MagnifyingGlassIcon },
- { name: 'Search', href: '/search', icon: MagnifyingGlassIcon },
- { name: 'Jurisdictions', href: '/jurisdictions', icon: MapPinIcon },
- {
- section: 'Families & Individuals',
- items: [
- { name: 'Community Events', href: '/events', icon: BookOpenIcon },
- { name: 'Services & Resources', href: '/services', icon: HeartIcon },
- ]
- },
- {
- section: 'Policy & Government',
- items: [
- { name: 'Policy Decisions', href: '/documents', icon: DocumentTextIcon },
- { name: 'Budget Analysis', href: '/analytics', icon: ChartBarIcon },
- { name: 'Elected Officials', href: '/people', icon: UserGroupIcon },
- { name: 'Demographics', href: '/heatmap', icon: MapIcon },
- ]
- },
- {
- section: 'Community & Advocacy',
- items: [
- { name: 'Nonprofits', href: '/nonprofits', icon: BuildingLibraryIcon },
- { name: 'Advocacy Topics', href: '/advocacy-topics', icon: BellAlertIcon },
- { name: 'Fact-Checking', href: '/fact-checking', icon: AcademicCapIcon },
- ]
- },
- {
- section: 'Developers',
- items: [
- { name: 'Open Source', href: '/opensource', icon: CodeBracketIcon },
- { name: 'Hackathons', href: '/hackathons', icon: AcademicCapIcon },
- ]
- },
- { name: 'Settings', href: '/settings', icon: Cog6ToothIcon },
-]
-
-export default function Layout() {
- const location = useLocation()
- const navigate = useNavigate()
- const [searchQuery, setSearchQuery] = useState('')
- const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
- const [showLoginMenu, setShowLoginMenu] = useState(false)
- const { user, isAuthenticated, login, logout, isLoading } = useAuth()
- const { location: userLocation, hasLocation } = useLocationContext()
-
- // Environment-aware URLs
- const docsUrl = import.meta.env.PROD ? '/docs/intro' : 'http://localhost:3000/docs/intro'
- const apiDocsUrl = import.meta.env.PROD ? '/api/docs' : 'http://localhost:8000/docs'
-
- const handleSearch = (e: React.FormEvent) => {
- e.preventDefault()
- if (searchQuery.trim()) {
- navigate(`/search?q=${encodeURIComponent(searchQuery)}`)
- }
- }
-
- return (
-
- {/* Top Header Bar */}
-
-
-
- {/* Mobile menu button */}
-
setMobileMenuOpen(!mobileMenuOpen)}
- className="md:hidden p-2 rounded-lg hover:bg-gray-100 text-gray-700"
- aria-label="Toggle menu"
- >
- {mobileMenuOpen ? (
-
- ) : (
-
- )}
-
-
-
-
-
- Open Navigator
-
-
-
-
- {/* Global Search - Hidden on home page and mobile */}
- {location.pathname !== '/' && (
-
- )}
-
- {/* Header Actions */}
-
- {/* Location Banner - Compact */}
- {hasLocation && userLocation && (
-
-
-
-
- {userLocation.city}, {userLocation.state}
-
- {userLocation.county && (
-
{userLocation.county}
- )}
-
-
navigate('/?tab=community')}
- className="text-xs text-primary-600 hover:text-primary-700 font-medium underline ml-2 flex-shrink-0"
- >
- Change
-
-
- )}
- {/* Authentication */}
- {isLoading ? (
-
- ) : isAuthenticated && user ? (
-
-
- {user.avatar_url ? (
- {
- // If image fails to load, hide it and show fallback
- e.currentTarget.style.display = 'none';
- const fallback = e.currentTarget.nextElementSibling as HTMLElement | null;
- if (fallback) fallback.style.display = 'flex';
- }}
- />
- ) : null}
-
- {(user.full_name || user.username || user.email).charAt(0).toUpperCase()}
-
-
- {user.full_name || user.username || user.email.split('@')[0]}
-
-
-
-
-
-
-
-
- {user.avatar_url ? (
-
{
- // If image fails to load, hide it and show fallback
- e.currentTarget.style.display = 'none';
- const fallback = e.currentTarget.nextElementSibling as HTMLElement | null;
- if (fallback) fallback.style.display = 'flex';
- }}
- />
- ) : null}
-
- {(user.full_name || user.username || user.email).charAt(0).toUpperCase()}
-
-
-
- {user.full_name || user.username || user.email.split('@')[0]}
-
-
- {user.email}
-
-
-
- {user.oauth_provider && (
-
- Signed in via
- {user.oauth_provider}
-
- )}
-
-
-
- {({ active }) => (
- navigate('/profile')}
- className={`${
- active ? 'bg-gray-50' : ''
- } flex items-center gap-3 w-full px-4 py-2.5 text-sm text-gray-700 hover:text-gray-900`}
- >
-
- My Profile
-
- )}
-
-
- {({ active }) => (
- navigate('/settings')}
- className={`${
- active ? 'bg-gray-50' : ''
- } flex items-center gap-3 w-full px-4 py-2.5 text-sm text-gray-700 hover:text-gray-900`}
- >
-
- Settings
-
- )}
-
-
- {({ active }) => (
-
-
- Sign out
-
- )}
-
-
-
-
-
- ) : (
-
-
setShowLoginMenu(!showLoginMenu)}
- className="px-3 md:px-4 py-2 text-white rounded-lg transition-colors text-sm md:text-base font-medium flex items-center gap-2"
- style={{ backgroundColor: '#354F52' }}
- onMouseEnter={(e) => e.currentTarget.style.backgroundColor = '#2e4346'}
- onMouseLeave={(e) => e.currentTarget.style.backgroundColor = '#354F52'}
- >
-
- Register
-
-
-
- {showLoginMenu && (
-
-
-
{ login('google'); setShowLoginMenu(false); }}
- className="flex items-center gap-3 w-full px-4 py-3 hover:bg-gray-100 transition-colors"
- >
-
- Google
-
-
{ login('facebook'); setShowLoginMenu(false); }}
- className="flex items-center gap-3 w-full px-4 py-3 hover:bg-gray-100 transition-colors"
- >
-
- Facebook
-
-
{ login('github'); setShowLoginMenu(false); }}
- className="flex items-center gap-3 w-full px-4 py-3 hover:bg-gray-100 transition-colors"
- >
-
- GitHub
-
-
-
{ login('huggingface'); setShowLoginMenu(false); }}
- className="flex items-center gap-3 w-full px-4 py-3 hover:bg-gray-100 transition-colors"
- >
-
- 🤗
-
- HuggingFace
-
-
- )}
-
- )}
-
-
-
- Docs
-
-
e.currentTarget.style.backgroundColor = '#2e4346'}
- onMouseLeave={(e) => e.currentTarget.style.backgroundColor = '#354F52'}
- >
- API
-
-
-
-
-
- {/* Sidebar */}
-
-
- {navigation.map((item, index) => {
- // Handle section headers with nested items
- if ('section' in item && item.section && item.items) {
- return (
-
-
- {item.section}
-
- {item.items.map((subItem) => {
- const isActive = location.pathname === subItem.href
- const isExternal = 'external' in subItem && subItem.external
-
- const linkClasses = `
- flex items-center gap-3 px-4 py-3 mb-1 rounded-lg transition-colors
- ${
- isActive
- ? 'bg-primary-50 text-primary-700 font-medium'
- : 'text-gray-700 hover:bg-gray-100'
- }
- `
-
- if (isExternal) {
- return (
-
-
- {subItem.name}
-
- )
- }
-
- return (
-
setMobileMenuOpen(false)}
- className={linkClasses}
- >
-
-
{subItem.name}
-
- )
- })}
-
- )
- }
-
- // Handle regular navigation items
- if ('href' in item && item.href) {
- const isActive = location.pathname === item.href
- return (
- setMobileMenuOpen(false)}
- className={`
- flex items-center gap-3 px-4 py-3 mb-2 rounded-lg transition-colors
- ${
- isActive
- ? 'bg-primary-50 text-primary-700 font-medium'
- : 'text-gray-700 hover:bg-gray-100'
- }
- `}
- >
-
- {item.name}
-
- )
- }
-
- return null
- })}
-
-
- {/* Sidebar Footer */}
-
-
-
Open Data Sources
-
- • 925 Jurisdictions
- • 43,726 Nonprofits
- • 6,913 Meeting Pages
- • 362 Officials
-
-
-
- 📍 Request Jurisdiction Coverage
-
-
-
-
-
-
- {/* Mobile menu overlay */}
- {mobileMenuOpen && (
-
setMobileMenuOpen(false)}
- />
- )}
-
- {/* Main content */}
-
-
-
-
-
-
- )
-}
diff --git a/frontend/src/components/RegistrationModal.tsx b/frontend/src/components/RegistrationModal.tsx
deleted file mode 100644
index 5fa88a9afbb501b63baf67fa6e3cfaa8c3369211..0000000000000000000000000000000000000000
--- a/frontend/src/components/RegistrationModal.tsx
+++ /dev/null
@@ -1,216 +0,0 @@
-import { Fragment, useState } from 'react'
-import { Dialog, Transition } from '@headlessui/react'
-import { XMarkIcon, MapPinIcon } from '@heroicons/react/24/outline'
-
-interface RegistrationModalProps {
- isOpen: boolean
- onClose: () => void
- onComplete: (data: LocationData) => void
- initialData?: {
- state?: string
- county?: string
- city?: string
- school_board?: string
- }
-}
-
-interface LocationData {
- state: string
- county: string
- city: string
- school_board: string
-}
-
-export default function RegistrationModal({ isOpen, onClose, onComplete, initialData }: RegistrationModalProps) {
- const [formData, setFormData] = useState
({
- state: initialData?.state || '',
- county: initialData?.county || '',
- city: initialData?.city || '',
- school_board: initialData?.school_board || '',
- })
-
- const [errors, setErrors] = useState>({})
-
- const handleSubmit = (e: React.FormEvent) => {
- e.preventDefault()
-
- // Validate required fields
- const newErrors: Record = {}
- if (!formData.state.trim()) newErrors.state = 'State is required'
- if (!formData.county.trim()) newErrors.county = 'County is required'
- if (!formData.city.trim()) newErrors.city = 'City is required'
-
- if (Object.keys(newErrors).length > 0) {
- setErrors(newErrors)
- return
- }
-
- onComplete(formData)
- }
-
- const handleChange = (field: keyof LocationData, value: string) => {
- setFormData(prev => ({ ...prev, [field]: value }))
- // Clear error for this field
- if (errors[field]) {
- setErrors(prev => {
- const newErrors = { ...prev }
- delete newErrors[field]
- return newErrors
- })
- }
- }
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Complete Your Profile
-
-
-
-
-
-
-
-
- Help us personalize your experience by telling us where you're located.
- You can update this information anytime in Settings.
-
-
-
-
-
-
-
-
-
- )
-}
diff --git a/frontend/src/components/ScrollToTop.tsx b/frontend/src/components/ScrollToTop.tsx
deleted file mode 100644
index 9e3aec0633b794b09b859c5ee4375ef975dca7fc..0000000000000000000000000000000000000000
--- a/frontend/src/components/ScrollToTop.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { useEffect } from 'react'
-import { useLocation } from 'react-router-dom'
-
-/**
- * ScrollToTop component that automatically scrolls to the top of the page
- * when the route changes or the page is refreshed.
- */
-export default function ScrollToTop() {
- const { pathname } = useLocation()
-
- useEffect(() => {
- window.scrollTo(0, 0)
- }, [pathname])
-
- return null
-}
diff --git a/frontend/src/components/SocialStats.tsx b/frontend/src/components/SocialStats.tsx
deleted file mode 100644
index a52919a8f3639bbe672de53c8dc4d6338b0ed2b1..0000000000000000000000000000000000000000
--- a/frontend/src/components/SocialStats.tsx
+++ /dev/null
@@ -1,121 +0,0 @@
-import { useQuery } from '@tanstack/react-query'
-import axios from 'axios'
-import { UserGroupIcon, UserPlusIcon } from '@heroicons/react/24/outline'
-import { Link } from 'react-router-dom'
-
-interface SocialStatsProps {
- userId?: number
- showBreakdown?: boolean
- clickable?: boolean
-}
-
-interface FollowerStats {
- followers: number
- following: number
- following_users: number
- following_leaders: number
- following_organizations: number
- following_causes: number
-}
-
-export default function SocialStats({
- userId,
- showBreakdown = false,
- clickable = true
-}: SocialStatsProps) {
- const { data: stats, isLoading } = useQuery({
- queryKey: ['social', 'stats', userId],
- queryFn: async () => {
- const params = userId ? `?user_id=${userId}` : ''
- const response = await axios.get(`/api/social/stats${params}`)
- return response.data
- }
- })
-
- if (isLoading) {
- return (
-
- )
- }
-
- if (!stats) return null
-
- const StatsContent = () => (
- <>
- {/* LinkedIn-style stat display */}
-
-
-
-
- {stats.followers.toLocaleString()}
-
-
- {stats.followers === 1 ? 'Follower' : 'Followers'}
-
-
-
-
-
-
- {stats.following.toLocaleString()}
-
- Following
-
-
-
- {/* Breakdown of what they're following */}
- {showBreakdown && stats.following > 0 && (
-
- {stats.following_leaders > 0 && (
-
-
- {stats.following_leaders}
-
-
Leaders
-
- )}
-
- {stats.following_organizations > 0 && (
-
-
- {stats.following_organizations}
-
-
Charities
-
- )}
-
- {stats.following_causes > 0 && (
-
-
- {stats.following_causes}
-
-
Causes
-
- )}
-
- {stats.following_users > 0 && (
-
-
- {stats.following_users}
-
-
People
-
- )}
-
- )}
- >
- )
-
- if (clickable) {
- return (
-
-
-
- )
- }
-
- return
-}
diff --git a/frontend/src/contexts/AuthContext.tsx b/frontend/src/contexts/AuthContext.tsx
deleted file mode 100644
index b16119f913098a1b804bd27554f2758b0ff3d7b9..0000000000000000000000000000000000000000
--- a/frontend/src/contexts/AuthContext.tsx
+++ /dev/null
@@ -1,124 +0,0 @@
-import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
-
-interface User {
- id: number;
- email: string;
- username?: string;
- full_name?: string;
- avatar_url?: string;
- oauth_provider?: string;
- state?: string;
- county?: string;
- city?: string;
- school_board?: string;
- profile_completed?: boolean;
-}
-
-interface AuthContextType {
- user: User | null;
- token: string | null;
- login: (provider: string) => void;
- logout: () => void;
- isAuthenticated: boolean;
- isLoading: boolean;
-}
-
-const AuthContext = createContext(undefined);
-
-export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
- const [user, setUser] = useState(null);
- const [token, setToken] = useState(null);
- const [isLoading, setIsLoading] = useState(true);
-
- const API_URL = import.meta.env.PROD
- ? '/api'
- : 'http://localhost:8000';
-
- // Load user from localStorage on mount
- useEffect(() => {
- // Check for token in URL FIRST (OAuth callback)
- const urlParams = new URLSearchParams(window.location.search);
- const urlToken = urlParams.get('token');
-
- if (urlToken) {
- localStorage.setItem('auth_token', urlToken);
- setToken(urlToken);
- fetchUser(urlToken);
-
- // Clean URL (remove token from address bar)
- window.history.replaceState({}, document.title, window.location.pathname);
- return; // Exit early, fetchUser will handle loading state
- }
-
- // Check for stored token
- const storedToken = localStorage.getItem('auth_token');
-
- if (storedToken) {
- setToken(storedToken);
- fetchUser(storedToken);
- } else {
- setIsLoading(false);
- }
- }, []);
-
- const fetchUser = async (authToken: string) => {
- try {
- const response = await fetch(`${API_URL}/auth/me`, {
- headers: {
- 'Authorization': `Bearer ${authToken}`,
- },
- });
-
- if (response.ok) {
- const userData = await response.json();
- setUser(userData);
- } else {
- // Token is invalid
- localStorage.removeItem('auth_token');
- setToken(null);
- }
- } catch (error) {
- console.error('Error fetching user:', error);
- localStorage.removeItem('auth_token');
- setToken(null);
- } finally {
- setIsLoading(false);
- }
- };
-
- const login = (provider: string) => {
- // Redirect to OAuth endpoint
- const redirectUri = encodeURIComponent(window.location.origin);
- const authUrl = `${API_URL}/auth/login/${provider}?redirect_uri=${redirectUri}`;
- window.location.href = authUrl;
- };
-
- const logout = () => {
- localStorage.removeItem('auth_token');
- setToken(null);
- setUser(null);
- };
-
- return (
-
- {children}
-
- );
-};
-
-export const useAuth = () => {
- const context = useContext(AuthContext);
- if (context === undefined) {
- throw new Error('useAuth must be used within an AuthProvider');
- }
- return context;
-};
diff --git a/frontend/src/contexts/LocationContext.tsx b/frontend/src/contexts/LocationContext.tsx
deleted file mode 100644
index 23b583219488667f3b00f6a9be3a0a09e403468e..0000000000000000000000000000000000000000
--- a/frontend/src/contexts/LocationContext.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'
-import { useAuth } from './AuthContext'
-import { stateNameToCode } from '../utils/stateMapping'
-
-interface LocationData {
- address?: string
- state: string
- county: string
- city: string
- school_board?: string
- latitude?: number
- longitude?: number
-}
-
-interface LocationContextType {
- location: LocationData | null
- setLocation: (location: LocationData) => void
- clearLocation: () => void
- hasLocation: boolean
-}
-
-const LocationContext = createContext(undefined)
-
-export const LocationProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
- const { user, isAuthenticated } = useAuth()
- const [location, setLocationState] = useState(null)
-
- // Load location from user profile or localStorage
- useEffect(() => {
- if (isAuthenticated && user) {
- // Use location from user profile if available
- if (user.state && user.city) {
- // Migrate full state names to state codes
- const stateCode = stateNameToCode(user.state)
-
- setLocationState({
- state: stateCode,
- county: user.county || '',
- city: user.city,
- school_board: user.school_board,
- })
- }
- } else {
- // Load from localStorage for anonymous users
- const savedLocation = localStorage.getItem('user_location')
- if (savedLocation) {
- try {
- const parsed = JSON.parse(savedLocation)
-
- // Migrate full state names to state codes
- if (parsed.state) {
- parsed.state = stateNameToCode(parsed.state)
- }
-
- setLocationState(parsed)
-
- // Save back the migrated version
- localStorage.setItem('user_location', JSON.stringify(parsed))
- } catch (e) {
- console.error('Failed to parse saved location:', e)
- }
- }
- }
- }, [user, isAuthenticated])
-
- const setLocation = (newLocation: LocationData) => {
- setLocationState(newLocation)
-
- // Save to localStorage for anonymous users
- if (!isAuthenticated) {
- localStorage.setItem('user_location', JSON.stringify(newLocation))
- }
- }
-
- const clearLocation = () => {
- setLocationState(null)
- localStorage.removeItem('user_location')
- }
-
- const hasLocation = location !== null && !!location.state && !!location.city
-
- return (
-
- {children}
-
- )
-}
-
-export const useLocation = () => {
- const context = useContext(LocationContext)
- if (context === undefined) {
- throw new Error('useLocation must be used within a LocationProvider')
- }
- return context
-}
diff --git a/frontend/src/index.css b/frontend/src/index.css
deleted file mode 100644
index 8bac7379563f5ad88550bab0b0ed02a8700679fe..0000000000000000000000000000000000000000
--- a/frontend/src/index.css
+++ /dev/null
@@ -1,58 +0,0 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
-
-:root {
- font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
- line-height: 1.5;
- font-weight: 400;
- color-scheme: light dark;
- color: rgba(255, 255, 255, 0.87);
- background-color: #F1F5F9;
-}
-
-html {
- scroll-behavior: smooth;
-}
-
-body {
- margin: 0;
- min-height: 100vh;
-}
-
-@layer base {
- h1 {
- @apply text-4xl font-bold;
- }
- h2 {
- @apply text-3xl font-semibold;
- }
- h3 {
- @apply text-2xl font-semibold;
- }
-}
-
-@layer components {
- .card {
- @apply bg-white rounded-lg shadow-md p-6;
- }
-
- .btn-primary {
- @apply bg-neutral-600 hover:bg-neutral-700 text-white font-medium py-2 px-4 rounded-lg transition-colors;
- }
-
- .btn-secondary {
- @apply bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium py-2 px-4 rounded-lg transition-colors;
- }
-}
-
-@keyframes slideUp {
- from {
- opacity: 0;
- transform: translateY(30px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
-}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
deleted file mode 100644
index 8ae669357972a6faca139acc817ec941432b1f09..0000000000000000000000000000000000000000
--- a/frontend/src/main.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import React from 'react'
-import ReactDOM from 'react-dom/client'
-import { BrowserRouter } from 'react-router-dom'
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
-import { AuthProvider } from './contexts/AuthContext'
-import { LocationProvider } from './contexts/LocationContext'
-import ScrollToTop from './components/ScrollToTop'
-import App from './App'
-import './index.css'
-
-const queryClient = new QueryClient({
- defaultOptions: {
- queries: {
- refetchOnWindowFocus: false,
- retry: 1,
- staleTime: 5 * 60 * 1000, // 5 minutes
- },
- },
-})
-
-ReactDOM.createRoot(document.getElementById('root')!).render(
-
-
-
-
-
-
-
-
-
-
-
- ,
-)
diff --git a/frontend/src/pages/AdvocacyTopics.tsx b/frontend/src/pages/AdvocacyTopics.tsx
deleted file mode 100644
index 9b203dffc901790e3c6cc4684dfba4fafda31d74..0000000000000000000000000000000000000000
--- a/frontend/src/pages/AdvocacyTopics.tsx
+++ /dev/null
@@ -1,229 +0,0 @@
-import {
- BellAlertIcon,
- MegaphoneIcon,
- HeartIcon,
- AcademicCapIcon,
- ShieldCheckIcon,
- HomeModernIcon,
- SparklesIcon,
- UserGroupIcon,
-} from '@heroicons/react/24/outline'
-import { Link } from 'react-router-dom'
-
-export default function AdvocacyTopics() {
- const topics = [
- {
- icon: HeartIcon,
- title: 'Oral Health',
- description: 'Track dental programs, fluoridation policies, and oral health initiatives in your community.',
- count: '5,200+ related meetings',
- color: '#DC143C',
- keywords: ['dental', 'fluoride', 'oral health', 'teeth'],
- },
- {
- icon: AcademicCapIcon,
- title: 'Education',
- description: 'Monitor school budgets, curriculum changes, and educational programs across districts.',
- count: '15,000+ related meetings',
- color: '#354F52',
- keywords: ['schools', 'education', 'curriculum', 'budget'],
- },
- {
- icon: HomeModernIcon,
- title: 'Housing & Development',
- description: 'Follow zoning decisions, affordable housing initiatives, and development projects.',
- count: '8,500+ related meetings',
- color: '#52796F',
- keywords: ['housing', 'zoning', 'development', 'affordable'],
- },
- {
- icon: ShieldCheckIcon,
- title: 'Public Safety',
- description: 'Stay informed on police budgets, fire department resources, and emergency services.',
- count: '12,000+ related meetings',
- color: '#84A98C',
- keywords: ['police', 'fire', 'safety', 'emergency'],
- },
- {
- icon: UserGroupIcon,
- title: 'Social Services',
- description: 'Track programs for seniors, families, mental health, and community support.',
- count: '7,800+ related meetings',
- color: '#CAD2C5',
- keywords: ['social services', 'mental health', 'seniors', 'families'],
- },
- {
- icon: SparklesIcon,
- title: 'Environment & Sustainability',
- description: 'Monitor climate initiatives, recycling programs, and environmental policies.',
- count: '6,400+ related meetings',
- color: '#52796F',
- keywords: ['environment', 'climate', 'sustainability', 'recycling'],
- },
- ]
-
- const howToAdvocate = [
- {
- step: '1',
- title: 'Find Your Topic',
- description: 'Browse advocacy topics or search for specific issues affecting your community.',
- },
- {
- step: '2',
- title: 'Track Meetings',
- description: 'Monitor upcoming government meetings where your topic will be discussed.',
- },
- {
- step: '3',
- title: 'Prepare Your Message',
- description: 'Use our talking points and data to craft compelling testimony.',
- },
- {
- step: '4',
- title: 'Make Your Voice Heard',
- description: 'Attend meetings, submit comments, or contact officials directly.',
- },
- ]
-
- return (
-
-
- {/* Header */}
-
-
-
-
- Advocacy Topics
-
-
-
- Track what your community is discussing. Find advocacy opportunities and get involved in local decision-making.
-
-
-
- {/* Search Banner */}
-
-
-
- Find Advocacy Opportunities in Your Area
-
-
- Search meeting minutes for topics that matter to you. Get alerts when your issues are being discussed.
-
-
-
- Search Meeting Minutes
-
-
- View All Opportunities
-
-
-
-
- {/* Topics Grid */}
-
-
Popular Advocacy Topics
-
- {topics.map((topic) => {
- const Icon = topic.icon
- return (
-
-
-
-
-
-
- {topic.count}
-
-
-
- {topic.title}
-
-
- {topic.description}
-
-
- {topic.keywords.slice(0, 3).map((keyword) => (
-
- {keyword}
-
- ))}
-
-
- Search meetings →
-
-
- )
- })}
-
-
-
- {/* How to Advocate */}
-
-
How to Advocate Effectively
-
- {howToAdvocate.map((item) => (
-
-
- {item.step}
-
-
{item.title}
-
{item.description}
-
- ))}
-
-
-
- {/* Get Started CTA */}
-
-
- Ready to Make a Difference?
-
-
- Start by searching for your community and exploring what local government is discussing.
- Your voice matters in local decision-making.
-
-
-
- Find Your Community
-
-
- View Upcoming Meetings
-
-
-
-
-
- )
-}
diff --git a/frontend/src/pages/Analytics.tsx b/frontend/src/pages/Analytics.tsx
deleted file mode 100644
index 0fe6ebbca1858618c3a03c2ad991899ab6a05b2c..0000000000000000000000000000000000000000
--- a/frontend/src/pages/Analytics.tsx
+++ /dev/null
@@ -1,249 +0,0 @@
-import { useState } from 'react'
-import {
- ChartBarIcon,
- CurrencyDollarIcon,
- ArrowTrendingUpIcon,
- DocumentChartBarIcon,
-} from '@heroicons/react/24/outline'
-
-// Commented out for now - will be used when API is ready
-// import { useQuery } from '@tanstack/react-query'
-// import axios from 'axios'
-
-// interface BudgetData {
-// jurisdiction: string
-// state: string
-// fiscal_year: string
-// total_budget: number
-// budget_change: number
-// categories: {
-// education: number
-// infrastructure: number
-// public_safety: number
-// health: number
-// other: number
-// }
-// }
-
-export default function Analytics() {
- const [selectedState, setSelectedState] = useState('all')
- const [fiscalYear, setFiscalYear] = useState('2024')
-
- // Commented out for now - will be implemented when API is ready
- // const { data, isLoading } = useQuery({
- // queryKey: ['budget-analytics', selectedState, fiscalYear],
- // queryFn: async () => {
- // const response = await axios.get('/api/budgets', {
- // params: { state: selectedState, year: fiscalYear },
- // })
- // return response.data.budgets || []
- // },
- // })
-
- return (
-
-
- {/* Header */}
-
-
-
-
- Budget Analysis
-
-
-
- Explore city, county, and school budgets with budget-to-minutes delta analysis
-
-
-
- {/* Filter Controls */}
-
-
-
-
- State
-
- setSelectedState(e.target.value)}
- className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:outline-none"
- >
- All States
- Alabama
- Georgia
- Massachusetts
- Washington
- Wisconsin
-
-
-
-
- Fiscal Year
-
- setFiscalYear(e.target.value)}
- className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:outline-none"
- >
- FY 2024
- FY 2023
- FY 2022
-
-
-
-
-
- {/* Stats Overview */}
-
-
-
-
-
- Jurisdictions Tracked
-
-
- 90,000+
-
-
-
-
-
-
-
-
-
-
- Budget Records
-
-
- 15,000+
-
-
-
-
-
-
-
-
-
-
- Avg Budget Increase
-
-
- +3.2%
-
-
-
-
-
-
-
-
-
-
- States Covered
-
-
- 50
-
-
-
-
-
-
- {/* Features Section */}
-
-
-
- Budget-to-Minutes Delta Analysis
-
-
- Compare what governments say in meeting minutes versus what they actually allocate in budgets.
-
-
-
- ✓
- Track rhetoric vs. reality in budget decisions
-
-
- ✓
- Identify funding priorities and gaps
-
-
- ✓
- Monitor year-over-year changes
-
-
-
-
-
-
- Budget Categories Tracked
-
-
-
-
- Education & Schools
- 35%
-
-
-
-
-
- Public Safety
- 25%
-
-
-
-
-
- Infrastructure
- 20%
-
-
-
-
-
- Health & Human Services
- 15%
-
-
-
-
-
-
-
- {/* Coming Soon / Data Integration Notice */}
-
-
-
- Budget Data Integration In Progress
-
-
- We're currently integrating budget data from cities, counties, and school districts across all 50 states.
- Check back soon for interactive budget comparisons, trend analysis, and meeting-to-budget correlation insights.
-
-
-
-
-
- )
-}
diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx
deleted file mode 100644
index 99c10ddaf8b0a7cadc8bddfae09d99b2f4fb6640..0000000000000000000000000000000000000000
--- a/frontend/src/pages/Dashboard.tsx
+++ /dev/null
@@ -1,201 +0,0 @@
-import { useQuery } from '@tanstack/react-query'
-import axios from 'axios'
-import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell } from 'recharts'
-
-const COLORS = ['#0ea5e9', '#f59e0b', '#10b981', '#ef4444', '#8b5cf6']
-
-interface DashboardStats {
- total_documents: number
- total_opportunities: number
- states_monitored: number
- topics: Record
- recent_opportunities: Array<{
- state: string
- municipality: string
- topic: string
- urgency: string
- date: string
- }>
-}
-
-export default function Dashboard() {
- const { data, isLoading } = useQuery({
- queryKey: ['dashboard'],
- queryFn: async () => {
- const response = await axios.get('/api/dashboard')
- return response.data
- },
- })
-
- if (isLoading) {
- return (
-
- )
- }
-
- const topicData = Object.entries(data?.topics || {}).map(([name, value]) => ({
- name: name.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
- value,
- }))
-
- return (
-
-
- {/* Header */}
-
-
Data & Trends
-
- Statistics and insights across communities - see what's happening in local government
-
-
-
- {/* Stats Grid */}
-
-
-
Total Documents
-
- {data?.total_documents?.toLocaleString() || '0'}
-
-
Meeting minutes & budgets
-
-
-
-
Opportunities Found
-
- {data?.total_opportunities?.toLocaleString() || '0'}
-
-
Advocacy windows identified
-
-
-
-
States Monitored
-
- {data?.states_monitored || '0'}
-
-
Across the nation
-
-
-
- {/* Charts */}
-
- {/* Topics Bar Chart */}
-
-
Policy Topics
- {topicData.length > 0 ? (
-
-
-
-
-
-
-
-
-
- ) : (
-
- No topic data available
-
- )}
-
-
- {/* Topics Pie Chart */}
-
-
Topic Distribution
- {topicData.length > 0 ? (
-
-
- entry.name}
- outerRadius={80}
- fill="#8884d8"
- dataKey="value"
- >
- {topicData.map((_entry, index) => (
- |
- ))}
-
-
-
-
- ) : (
-
- No topic data available
-
- )}
-
-
-
- {/* Recent Opportunities */}
-
-
Recent Causes
- {data?.recent_opportunities && data.recent_opportunities.length > 0 ? (
-
-
-
-
-
- Location
-
-
- Topic
-
-
- Urgency
-
-
- Date
-
-
-
-
- {data.recent_opportunities.map((opp, idx) => (
-
-
- {opp.municipality}
- {opp.state}
-
-
- {opp.topic.replace(/_/g, ' ')}
-
-
-
- {opp.urgency}
-
-
-
- {new Date(opp.date).toLocaleDateString()}
-
-
- ))}
-
-
-
- ) : (
-
-
No opportunities found yet
-
- Run the data ingestion pipeline to analyze meetings and identify advocacy opportunities
-
-
- )}
-
-
-
- )
-}
diff --git a/frontend/src/pages/DebateGrader.tsx b/frontend/src/pages/DebateGrader.tsx
deleted file mode 100644
index 14e478b571f28cecb7f084f35ee44c0a42b79257..0000000000000000000000000000000000000000
--- a/frontend/src/pages/DebateGrader.tsx
+++ /dev/null
@@ -1,274 +0,0 @@
-import { useState } from 'react'
-import { CheckCircleIcon, XCircleIcon, QuestionMarkCircleIcon } from '@heroicons/react/24/outline'
-
-interface DebateGrade {
- dimensions: {
- harms: DimensionScore
- solvency: DimensionScore
- topicality: DimensionScore
- }
- overall: {
- score: number
- grade: string
- summary: string
- }
- timestamp: string
-}
-
-interface DimensionScore {
- score: number
- grade: string
- explanation: string
- layperson_label: string
- layperson_question: string
-}
-
-export default function DebateFinder() {
- const [text, setText] = useState('')
- const [title, setTitle] = useState('')
- const [grade, setGrade] = useState(null)
- const [loading, setLoading] = useState(false)
- const [error, setError] = useState('')
-
- const gradeDecision = async () => {
- if (!text) {
- setError('Please enter some text to grade')
- return
- }
-
- setLoading(true)
- setError('')
-
- try {
- const params = new URLSearchParams()
- params.set('text', text)
- if (title) params.set('title', title)
-
- const response = await fetch(`/api/debate-grade?${params.toString()}`, {
- method: 'POST'
- })
-
- if (!response.ok) {
- throw new Error('Failed to grade decision')
- }
-
- const data = await response.json()
- setGrade(data.debate_grade)
- } catch (err) {
- setError(err instanceof Error ? err.message : 'An error occurred')
- } finally {
- setLoading(false)
- }
- }
-
- const getGradeColor = (grade: string) => {
- switch (grade) {
- case 'excellent':
- return 'text-green-600'
- case 'good':
- return 'text-blue-600'
- case 'fair':
- return 'text-yellow-600'
- case 'weak':
- return 'text-orange-600'
- case 'missing':
- return 'text-red-600'
- default:
- return 'text-gray-600'
- }
- }
-
- const getGradeIcon = (grade: string) => {
- switch (grade) {
- case 'excellent':
- case 'good':
- return
- case 'fair':
- return
- case 'weak':
- case 'missing':
- return
- default:
- return null
- }
- }
-
- return (
-
-
-
-
- Debate Finder
-
-
- Evaluate government decisions using debate framework: Harms, Solvency, and Topicality
-
-
-
- {/* Input Form */}
-
-
-
-
- Decision Title (optional)
-
- setTitle(e.target.value)}
- className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500"
- placeholder="e.g., City Council approves dental screening program"
- />
-
-
-
-
- Decision Text
-
-
-
- {error && (
-
- {error}
-
- )}
-
-
e.currentTarget.style.backgroundColor = '#2e4346'}
- onMouseLeave={(e) => e.currentTarget.style.backgroundColor = '#354F52'}
- >
- {loading ? 'Grading...' : 'Grade This Decision'}
-
-
-
-
- {/* Results */}
- {grade && (
-
- {/* Overall Score */}
-
-
- Overall Grade:
- {grade.overall.grade.toUpperCase()}
-
-
-
- Score: {grade.overall.score}/5
-
-
- {grade.overall.summary}
-
-
-
- {/* Dimension Breakdown */}
-
- {/* Harms */}
-
-
-
- {grade.dimensions.harms.layperson_label}
-
- {getGradeIcon(grade.dimensions.harms.grade)}
-
-
- "{grade.dimensions.harms.layperson_question}"
-
-
-
- {grade.dimensions.harms.grade.toUpperCase()}
-
-
- ({grade.dimensions.harms.score}/5)
-
-
-
- {grade.dimensions.harms.explanation}
-
-
-
- {/* Solvency */}
-
-
-
- {grade.dimensions.solvency.layperson_label}
-
- {getGradeIcon(grade.dimensions.solvency.grade)}
-
-
- "{grade.dimensions.solvency.layperson_question}"
-
-
-
- {grade.dimensions.solvency.grade.toUpperCase()}
-
-
- ({grade.dimensions.solvency.score}/5)
-
-
-
- {grade.dimensions.solvency.explanation}
-
-
-
- {/* Topicality */}
-
-
-
- {grade.dimensions.topicality.layperson_label}
-
- {getGradeIcon(grade.dimensions.topicality.grade)}
-
-
- "{grade.dimensions.topicality.layperson_question}"
-
-
-
- {grade.dimensions.topicality.grade.toUpperCase()}
-
-
- ({grade.dimensions.topicality.score}/5)
-
-
-
- {grade.dimensions.topicality.explanation}
-
-
-
-
- {/* Explanation */}
-
-
- Understanding the Debate Framework
-
-
-
- The Problem (Harms): Does the decision clearly explain the crisis or problem?
- Is there data to back it up? Who is affected?
-
-
- The Fix (Solvency): Does the solution actually work? Is there a clear plan?
- Has it worked elsewhere?
-
-
- The Scope (Topicality): Does the government body have the legal authority to do this?
- Is it within their jurisdiction?
-
-
-
-
- )}
-
-
- )
-}
diff --git a/frontend/src/pages/Developers.tsx b/frontend/src/pages/Developers.tsx
deleted file mode 100644
index 02dc6c3920451005f631bab257459ade33bfe585..0000000000000000000000000000000000000000
--- a/frontend/src/pages/Developers.tsx
+++ /dev/null
@@ -1,200 +0,0 @@
-import { CodeBracketIcon, RocketLaunchIcon, BookOpenIcon, CubeIcon, ServerIcon, CircleStackIcon } from '@heroicons/react/24/outline';
-
-export default function Developers() {
- return (
-
- {/* Page Header */}
-
-
-
-
-
-
-
Developers & Civic Tech
-
Build civic tech solutions with open data and open source tools
-
-
-
-
- {/* Hero Section */}
-
-
Build the Future of Civic Engagement
-
- Open Navigator is 100% open source. Join developers worldwide building tools for transparency,
- accountability, and community empowerment.
-
-
-
-
- {/* Tech Stack */}
-
-
Tech Stack
-
- {/* Frontend */}
-
-
-
- • React 18 + TypeScript
- • Vite + Tailwind CSS
- • TanStack Query
- • Recharts for visualizations
-
-
-
- {/* Backend */}
-
-
-
- • Python FastAPI
- • LangChain + LangGraph
- • OpenAI, Anthropic APIs
- • Supabase Auth
-
-
-
- {/* Data */}
-
-
-
- • 925 jurisdictions
- • 43,726 nonprofits
- • 6,913 meeting pages
- • Medallion architecture
-
-
-
-
-
- {/* Quick Links */}
-
-
- {/* API Example */}
-
-
-
- API Example
-
-
- {`# Search meeting transcripts
-import requests
-
-response = requests.get(
- 'http://localhost:8000/api/search/',
- params={
- 'q': 'dental health',
- 'state': 'AL',
- 'limit': 10
- }
-)
-
-for result in response.json()['results']:
- print(f"{result['title']} - {result['date']}")
- print(f"Score: {result['score']}")
- print(result['snippet'])
- print()`}
-
-
-
- );
-}
diff --git a/frontend/src/pages/Documents.tsx b/frontend/src/pages/Documents.tsx
deleted file mode 100644
index 51cad54e9c13cff6003d70998e23d862b6914858..0000000000000000000000000000000000000000
--- a/frontend/src/pages/Documents.tsx
+++ /dev/null
@@ -1,233 +0,0 @@
-import { useState, useEffect } from 'react'
-import { useQuery } from '@tanstack/react-query'
-import { useSearchParams } from 'react-router-dom'
-import axios from 'axios'
-import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'
-
-interface Document {
- id: string
- state: string
- municipality: string
- title: string
- meeting_date: string
- url: string
- topics: string[]
- sentiment: string
-}
-
-export default function Documents() {
- const [searchParams, setSearchParams] = useSearchParams()
- const [searchQuery, setSearchQuery] = useState(searchParams.get('search') || '')
- const [page, setPage] = useState(1)
- const [activeFilter, setActiveFilter] = useState('all') // all, meetings, budgets, people, organizations
-
- useEffect(() => {
- const searchFromUrl = searchParams.get('search')
- if (searchFromUrl) {
- setSearchQuery(searchFromUrl)
- }
- }, [searchParams])
-
- const { data, isLoading } = useQuery({
- queryKey: ['documents', searchQuery, page],
- queryFn: async () => {
- const response = await axios.get('/api/documents', {
- params: { search: searchQuery, page, limit: 20 },
- })
- return response.data
- },
- })
-
- const handleSearch = (e: React.FormEvent) => {
- e.preventDefault()
- setPage(1)
- if (searchQuery) {
- setSearchParams({ search: searchQuery })
- } else {
- setSearchParams({})
- }
- }
-
- return (
-
-
- {/* Header */}
-
-
Meeting Minutes
-
- Search what local governments are discussing - 90,000+ cities, counties, and school districts
-
-
-
- {/* Search Bar */}
-
-
- {/* Filter Pills */}
-
-
-
setActiveFilter('all')}
- className={`px-4 py-2 rounded-full font-medium transition-colors ${
- activeFilter === 'all'
- ? 'bg-primary-600 text-white'
- : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
- }`}
- >
- All
-
-
setActiveFilter('meetings')}
- className={`px-4 py-2 rounded-full font-medium transition-colors ${
- activeFilter === 'meetings'
- ? 'bg-primary-600 text-white'
- : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
- }`}
- >
- Meeting Minutes
-
-
setActiveFilter('budgets')}
- className={`px-4 py-2 rounded-full font-medium transition-colors ${
- activeFilter === 'budgets'
- ? 'bg-primary-600 text-white'
- : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
- }`}
- >
- Budgets
-
-
setActiveFilter('people')}
- className={`px-4 py-2 rounded-full font-medium transition-colors ${
- activeFilter === 'people'
- ? 'bg-primary-600 text-white'
- : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
- }`}
- >
- People
-
-
setActiveFilter('organizations')}
- className={`px-4 py-2 rounded-full font-medium transition-colors ${
- activeFilter === 'organizations'
- ? 'bg-primary-600 text-white'
- : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
- }`}
- >
- Organizations
-
-
setActiveFilter('charities')}
- className={`px-4 py-2 rounded-full font-medium transition-colors ${
- activeFilter === 'charities'
- ? 'bg-primary-600 text-white'
- : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
- }`}
- >
- Charities
-
-
setActiveFilter('events')}
- className={`px-4 py-2 rounded-full font-medium transition-colors ${
- activeFilter === 'events'
- ? 'bg-primary-600 text-white'
- : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
- }`}
- >
- Events
-
-
-
-
-
- All filters
-
-
-
-
- {/* Results */}
-
- {isLoading ? (
-
Loading documents...
- ) : (
- <>
- {data?.documents?.map((doc: Document) => (
-
-
-
-
{doc.title}
-
- {doc.municipality}, {doc.state} • {new Date(doc.meeting_date).toLocaleDateString()}
-
-
-
- {doc.topics.map((topic) => (
-
- {topic.replace(/_/g, ' ')}
-
- ))}
-
-
-
-
- View Document
-
-
-
- ))}
-
- {/* Pagination */}
-
- setPage(page - 1)}
- disabled={page === 1}
- className="btn-secondary disabled:opacity-50"
- >
- Previous
-
-
- Page {page} of {data?.total_pages || 1}
-
- setPage(page + 1)}
- disabled={page >= (data?.total_pages || 1)}
- className="btn-secondary disabled:opacity-50"
- >
- Next
-
-
- >
- )}
-
-
-
- )
-}
diff --git a/frontend/src/pages/Events.tsx b/frontend/src/pages/Events.tsx
deleted file mode 100644
index ab8e56168aef5c27a63f75206d3a8e04ae036bca..0000000000000000000000000000000000000000
--- a/frontend/src/pages/Events.tsx
+++ /dev/null
@@ -1,116 +0,0 @@
-import { CalendarIcon, MapPinIcon, UserGroupIcon, ClockIcon } from '@heroicons/react/24/outline';
-
-export default function Events() {
- return (
-
- {/* Page Header */}
-
-
-
-
-
-
-
Community Events
-
Discover upcoming public meetings, community gatherings, and civic events
-
-
-
-
- {/* Coming Soon Notice */}
-
-
-
-
-
-
-
Coming Soon!
-
- We're building a comprehensive events calendar to help you stay engaged with your community.
-
-
-
What you'll find here:
-
- City council meetings, school board sessions, and public hearings
- Community forums, town halls, and neighborhood gatherings
- Voter registration drives and civic engagement events
- Training workshops and educational programs
-
-
-
-
-
-
- {/* Preview Cards */}
-
- {/* Example Event 1 */}
-
-
-
City Council Meeting
-
- Upcoming
-
-
-
-
-
- Tuesday, May 5, 2026 at 6:00 PM
-
-
-
- City Hall, Main Chamber
-
-
-
- Open to the public
-
-
-
-
- Budget discussions, zoning changes, and community feedback session
-
-
-
-
- {/* Example Event 2 */}
-
-
-
Community Health Fair
-
- Registration Open
-
-
-
-
-
- Saturday, May 10, 2026 at 10:00 AM
-
-
-
- Community Center, 123 Main St
-
-
-
- Free for families
-
-
-
-
- Free dental screenings, health resources, and family activities
-
-
-
-
-
- {/* Temporary Link */}
-
-
- );
-}
diff --git a/frontend/src/pages/Explore.tsx b/frontend/src/pages/Explore.tsx
deleted file mode 100644
index dca2432d33b14b8bd571dbc420f49918934c180a..0000000000000000000000000000000000000000
--- a/frontend/src/pages/Explore.tsx
+++ /dev/null
@@ -1,502 +0,0 @@
-import { Link } from 'react-router-dom';
-import { useEffect } from 'react';
-import {
- UserGroupIcon,
- BuildingOfficeIcon,
- BriefcaseIcon,
- DocumentTextIcon,
- MapIcon,
- ChartBarIcon,
- MicrophoneIcon,
- BellAlertIcon,
- CalendarIcon,
- AcademicCapIcon,
- CheckBadgeIcon,
- PhoneIcon,
- ChatBubbleLeftRightIcon,
- HeartIcon,
- CodeBracketIcon,
- RocketLaunchIcon,
-} from '@heroicons/react/24/outline';
-
-interface ExploreCard {
- title: string;
- description: string;
- icon: React.ComponentType<{ className?: string }>;
- path: string;
- color: string;
- stats?: string;
-}
-
-// Policy Maker-focused sections
-const policyMakerOptions: ExploreCard[] = [
- {
- title: 'Policy Decisions',
- description: 'Track decisions, budget deltas, stakeholder positions, and deferral patterns across government meetings.',
- icon: DocumentTextIcon,
- path: '/documents',
- color: '#354F52',
- stats: '500K+ meeting pages',
- },
- {
- title: 'Budget Analysis',
- description: 'Explore city, county, and school budgets with budget-to-minutes delta analysis showing rhetoric vs. reality.',
- icon: ChartBarIcon,
- path: '/analytics',
- color: '#52796F',
- stats: 'Real-time tracking',
- },
- {
- title: 'Elected Officials',
- description: 'Find local, state, and school board officials with voting records and decision patterns.',
- icon: UserGroupIcon,
- path: '/people',
- color: '#84A98C',
- stats: '100K+ officials',
- },
- {
- title: 'Demographics & Data',
- description: 'Access Census demographics, income, education, housing, and health insurance data by jurisdiction.',
- icon: MapIcon,
- path: '/heatmap',
- color: '#CAD2C5',
- stats: '90K+ jurisdictions',
- },
-];
-
-// Community & Advocate-focused sections
-const advocateOptions: ExploreCard[] = [
- {
- title: 'Nonprofits & Churches',
- description: 'Search 43,726 nonprofits including 4,372 churches with financial data from 5 states.',
- icon: BuildingOfficeIcon,
- path: '/nonprofits',
- color: '#354F52',
- stats: '43,726 organizations',
- },
- {
- title: 'Advocacy Topics',
- description: 'Track what your community is discussing. Find advocacy alerts and engagement opportunities.',
- icon: BellAlertIcon,
- path: '/advocacy-topics',
- color: '#52796F',
- stats: 'Get involved',
- },
- {
- title: 'Grants & Funding',
- description: 'Discover government grants, foundation funding, and program delivery outcomes for nonprofits.',
- icon: BriefcaseIcon,
- path: '/analytics',
- color: '#84A98C',
- stats: 'Find funding',
- },
- {
- title: 'Fact-Checking',
- description: 'Verify claims from meetings and legislation with PolitiFact, FactCheck.org, and Google Fact Check data.',
- icon: MicrophoneIcon,
- path: '/fact-checking',
- color: '#CAD2C5',
- stats: 'Verified claims',
- },
-];
-
-// Developers-focused sections
-const developerOptions: ExploreCard[] = [
- {
- title: 'Open Source Projects',
- description: 'Contribute to civic tech, data pipelines, AI models, and open government tools.',
- icon: CodeBracketIcon,
- path: '/opensource',
- color: '#354F52',
- stats: 'Join the community',
- },
- {
- title: 'Hackathons for Good',
- description: 'Build solutions for civic engagement, transparency, and community empowerment at our quarterly hackathons.',
- icon: RocketLaunchIcon,
- path: '/hackathons',
- color: '#52796F',
- stats: 'Make an impact',
- },
-];
-
-// Families & Individuals-focused sections
-const familyOptions: ExploreCard[] = [
- {
- title: 'Community Events',
- description: 'Discover local government meetings, public hearings, town halls, and community events you can attend.',
- icon: CalendarIcon,
- path: '/events',
- color: '#354F52',
- stats: 'Attend & engage',
- },
- {
- title: 'Training & Services',
- description: 'Find community programs, educational workshops, health services, and family support resources.',
- icon: AcademicCapIcon,
- path: '/services',
- color: '#52796F',
- stats: 'Learn & grow',
- },
- {
- title: 'Voter Registration',
- description: 'Register to vote, find your polling place, check registration status, and learn about candidates.',
- icon: CheckBadgeIcon,
- path: '/analytics?topic=elections',
- color: '#84A98C',
- stats: 'Make your voice heard',
- },
- {
- title: 'Contact Your Representatives',
- description: 'Find contact information for elected officials, city council members, and school board representatives.',
- icon: PhoneIcon,
- path: '/people?view=contact',
- color: '#CAD2C5',
- stats: '100K+ officials',
- },
- {
- title: 'Submit Feedback',
- description: 'Provide public comments, share concerns, and participate in community decision-making processes.',
- icon: ChatBubbleLeftRightIcon,
- path: '/opportunities?type=feedback',
- color: '#52796F',
- stats: 'Be heard',
- },
- {
- title: 'Community Resources',
- description: 'Access food banks, housing assistance, healthcare, childcare, and other family support services.',
- icon: HeartIcon,
- path: '/nonprofits?category=family-services',
- color: '#84A98C',
- stats: 'Get help',
- },
-];
-
-export default function Explore() {
- // Scroll to top with smooth behavior when page loads
- useEffect(() => {
- window.scrollTo({
- top: 0,
- behavior: 'smooth'
- });
- }, []);
-
- return (
-
- {/* Header */}
-
-
-
-
- Explore Open Navigator Data
-
-
- Access comprehensive data on government decisions, budgets, demographics, nonprofits, and community engagement.
-
-
-
-
-
- {/* Families & Individuals Section - FIRST */}
-
-
-
For Families & Individuals
-
Events, training, services, voter registration, and ways to engage with your community.
-
-
-
- {familyOptions.map((option) => {
- const Icon = option.icon;
- return (
-
-
- {/* Icon */}
-
-
- {/* Title and Stats */}
-
-
- {option.title}
-
- {option.stats && (
-
- {option.stats}
-
- )}
-
-
- {/* Description */}
-
- {option.description}
-
-
- {/* Arrow indicator */}
-
-
- Explore →
-
-
-
-
- {/* Hover effect bar */}
-
-
- );
- })}
-
-
- {/* Policy Makers Section */}
-
-
For Policy Makers & Government
-
Track decisions, budgets, officials, and demographic data across 90,000+ jurisdictions.
-
-
-
- {policyMakerOptions.map((option) => {
- const Icon = option.icon;
- return (
-
-
- {/* Icon */}
-
-
- {/* Title and Stats */}
-
-
- {option.title}
-
- {option.stats && (
-
- {option.stats}
-
- )}
-
-
- {/* Description */}
-
- {option.description}
-
-
- {/* Arrow indicator */}
-
-
- Explore →
-
-
-
-
- {/* Hover effect bar */}
-
-
- );
- })}
-
-
- {/* Advocates Section */}
-
-
For Advocates & Community Members
-
Find nonprofits, advocacy topics, funding, and fact-checked information.
-
-
-
- {advocateOptions.map((option) => {
- const Icon = option.icon;
- return (
-
-
- {/* Icon */}
-
-
- {/* Title and Stats */}
-
-
- {option.title}
-
- {option.stats && (
-
- {option.stats}
-
- )}
-
-
- {/* Description */}
-
- {option.description}
-
-
- {/* Arrow indicator */}
-
-
- Explore →
-
-
-
-
- {/* Hover effect bar */}
-
-
- );
- })}
-
-
- {/* Developers Section */}
-
-
For Developers & Civic Tech
-
Build with open data, contribute to open source, and join hackathons for social good.
-
-
-
- {developerOptions.map((option) => {
- const Icon = option.icon;
- const isExternal = option.path.startsWith('http');
-
- const card = (
-
-
- {/* Icon */}
-
-
- {/* Title and Stats */}
-
-
- {option.title}
-
- {option.stats && (
-
- {option.stats}
-
- )}
-
-
- {/* Description */}
-
- {option.description}
-
-
- {/* Arrow indicator */}
-
-
- {isExternal ? 'View on GitHub →' : 'Explore →'}
-
-
-
-
- {/* Hover effect bar */}
-
-
- );
-
- return isExternal ? (
-
- {card}
-
- ) : (
-
- {card}
-
- );
- })}
-
-
- {/* Bottom CTA */}
-
-
-
- Access the Complete Dataset
-
-
- All data is available on HuggingFace with full documentation, APIs, and CSV/Parquet downloads.
-
-
-
-
-
-
- {/* Back to Home */}
-
-
- ← Back to Home
-
-
-
- );
-}
diff --git a/frontend/src/pages/FactChecking.tsx b/frontend/src/pages/FactChecking.tsx
deleted file mode 100644
index 2b8bf367f219df4c0b31bcae9707de7d2aaf28c6..0000000000000000000000000000000000000000
--- a/frontend/src/pages/FactChecking.tsx
+++ /dev/null
@@ -1,268 +0,0 @@
-import {
- MicrophoneIcon,
- CheckCircleIcon,
- XCircleIcon,
- QuestionMarkCircleIcon,
- ShieldCheckIcon,
- DocumentMagnifyingGlassIcon,
- ArrowTopRightOnSquareIcon,
-} from '@heroicons/react/24/outline'
-import { Link } from 'react-router-dom'
-
-export default function FactChecking() {
- const factCheckSources = [
- {
- name: 'PolitiFact',
- description: 'Fact-checking political claims with the Truth-O-Meter rating system.',
- url: 'https://www.politifact.com',
- logo: '🔍',
- focus: 'National & State Politics',
- },
- {
- name: 'FactCheck.org',
- description: 'Nonpartisan fact-checking by the Annenberg Public Policy Center.',
- url: 'https://www.factcheck.org',
- logo: '✓',
- focus: 'Political Claims',
- },
- {
- name: 'Google Fact Check Explorer',
- description: 'Search fact checks from publishers worldwide using Google\'s Fact Check Tools.',
- url: 'https://toolbox.google.com/factcheck/explorer',
- logo: '🔎',
- focus: 'Global Claims',
- },
- {
- name: 'Snopes',
- description: 'Fact-checking urban legends, internet rumors, and misinformation.',
- url: 'https://www.snopes.com',
- logo: '📰',
- focus: 'Internet & Media',
- },
- ]
-
- const howToFactCheck = [
- {
- icon: DocumentMagnifyingGlassIcon,
- title: 'Find the Claim',
- description: 'Identify specific statements made in government meetings or public discourse.',
- },
- {
- icon: ShieldCheckIcon,
- title: 'Check Credible Sources',
- description: 'Use fact-checking organizations and official data sources to verify claims.',
- },
- {
- icon: CheckCircleIcon,
- title: 'Evaluate Evidence',
- description: 'Look for primary sources, statistics, and expert analysis to support or refute claims.',
- },
- {
- icon: MicrophoneIcon,
- title: 'Share Findings',
- description: 'Report misinformation and share accurate information with your community.',
- },
- ]
-
- const claimCategories = [
- {
- type: 'True',
- icon: CheckCircleIcon,
- color: 'text-green-600',
- bgColor: 'bg-green-50',
- description: 'Claim is accurate and supported by evidence',
- },
- {
- type: 'Mostly True',
- icon: CheckCircleIcon,
- color: 'text-blue-600',
- bgColor: 'bg-blue-50',
- description: 'Claim is mostly accurate with minor omissions',
- },
- {
- type: 'Half True',
- icon: QuestionMarkCircleIcon,
- color: 'text-yellow-600',
- bgColor: 'bg-yellow-50',
- description: 'Claim contains elements of truth but lacks context',
- },
- {
- type: 'Mostly False',
- icon: XCircleIcon,
- color: 'text-orange-600',
- bgColor: 'bg-orange-50',
- description: 'Claim contains significant inaccuracies',
- },
- {
- type: 'False',
- icon: XCircleIcon,
- color: 'text-red-600',
- bgColor: 'bg-red-50',
- description: 'Claim is not supported by evidence',
- },
- ]
-
- return (
-
-
- {/* Header */}
-
-
-
-
- Fact-Checking Tools
-
-
-
- Verify claims from meetings and legislation with trusted fact-checking sources and tools.
-
-
-
- {/* Debate Grader Tool */}
-
-
-
-
- Debate Framework Analyzer
-
-
- Evaluate government decisions using a debate framework that analyzes Harms, Solvency, and Topicality.
-
-
-
- Try Debate Grader →
-
-
-
-
-
-
Harms Analysis
-
- Evaluates whether the decision addresses a real problem and its severity.
-
-
-
-
Solvency Check
-
- Assesses if the proposed solution will actually solve the identified problem.
-
-
-
-
Topicality Review
-
- Determines if the decision is within the authority and scope of the body making it.
-
-
-
-
-
- {/* Truth Rating Scale */}
-
-
Understanding Truth Ratings
-
- {claimCategories.map((category) => {
- const Icon = category.icon
- return (
-
-
-
-
{category.type}
-
{category.description}
-
-
- )
- })}
-
-
-
- {/* Fact-Checking Sources */}
-
-
Trusted Fact-Checking Resources
-
-
-
- {/* How to Fact Check */}
-
-
How to Fact-Check Claims
-
- {howToFactCheck.map((step) => {
- const Icon = step.icon
- return (
-
-
-
-
-
{step.title}
-
{step.description}
-
- )
- })}
-
-
-
- {/* Tips for Critical Thinking */}
-
-
Tips for Critical Thinking
-
-
-
💡
-
Question the Source: Who is making the claim? Do they have expertise or potential bias?
-
-
-
📊
-
Look for Data: Are there statistics or studies backing up the claim? Are they from credible sources?
-
-
-
🔍
-
Check Multiple Sources: Does the claim appear in multiple independent, reliable sources?
-
-
-
⏰
-
Consider Context: Is the claim taken out of context? When was it made, and is it still relevant?
-
-
-
🤔
-
Beware of Bias: Does the source have a political or financial incentive to make this claim?
-
-
-
-
-
- )
-}
diff --git a/frontend/src/pages/Hackathons.tsx b/frontend/src/pages/Hackathons.tsx
deleted file mode 100644
index fe06d4c840151f3b0938304cc88537bf7a6dd7ba..0000000000000000000000000000000000000000
--- a/frontend/src/pages/Hackathons.tsx
+++ /dev/null
@@ -1,218 +0,0 @@
-import { RocketLaunchIcon, CalendarIcon, TrophyIcon, UserGroupIcon, CodeBracketIcon, LightBulbIcon } from '@heroicons/react/24/outline';
-
-export default function Hackathons() {
- return (
-
- {/* Page Header */}
-
-
-
-
-
-
-
Hackathons for Good
-
Build civic tech solutions that empower communities and promote transparency
-
-
-
-
- {/* Coming Soon Notice */}
-
-
-
-
-
-
-
Quarterly Civic Tech Hackathons
-
- Join developers, designers, and civic advocates to build tools for social impact.
-
-
-
- Next Event: Coming Soon!
-
-
-
-
-
- {/* Why Participate */}
-
-
Why Participate?
-
-
-
-
- Build tools that help communities access services, hold government accountable, and participate in democracy.
-
-
-
-
-
-
- Work alongside civic advocates, policy experts, and fellow developers to solve real problems.
-
-
-
-
-
-
- Win prizes, get featured in our showcase, and add meaningful projects to your portfolio.
-
-
-
-
-
- {/* Challenge Tracks */}
-
-
Challenge Tracks
-
- {/* Track 1 */}
-
-
-
-
-
-
-
Data Visualization & Dashboards
-
- Create interactive visualizations that make government data accessible to everyday citizens.
-
-
-
- React
-
-
- D3.js
-
-
- Data Analysis
-
-
-
-
-
-
- {/* Track 2 */}
-
-
-
-
-
-
-
AI for Civic Engagement
-
- Use LLMs and AI agents to summarize meetings, extract policy insights, or answer citizen questions.
-
-
-
- LangChain
-
-
- RAG
-
-
- NLP
-
-
-
-
-
-
- {/* Track 3 */}
-
-
-
-
-
-
-
Community Engagement Tools
-
- Build mobile apps, notification systems, or tools that help people participate in local government.
-
-
-
- Mobile
-
-
- Notifications
-
-
- UX Design
-
-
-
-
-
-
-
-
- {/* Resources */}
-
-
Resources for Participants
-
-
-
Data Access
-
- • 925 jurisdiction records
- • 43,726 nonprofit organizations
- • 6,913 meeting pages with transcripts
- • API access and bulk downloads
-
-
-
-
Support
-
- • Mentors from civic tech community
- • Technical workshops and tutorials
- • GitHub repository with starter code
- • Discord community for collaboration
-
-
-
-
-
- {/* CTA */}
-
-
-
- Join Our Community
-
-
- Get notified about upcoming hackathons, workshops, and civic tech events.
-
-
-
-
-
- );
-}
diff --git a/frontend/src/pages/Heatmap.tsx b/frontend/src/pages/Heatmap.tsx
deleted file mode 100644
index 13df4a1e647ea1b560cec049f8469a630cf48518..0000000000000000000000000000000000000000
--- a/frontend/src/pages/Heatmap.tsx
+++ /dev/null
@@ -1,156 +0,0 @@
-import { useState } from 'react'
-import { MapContainer, TileLayer, CircleMarker, Popup } from 'react-leaflet'
-import { useQuery } from '@tanstack/react-query'
-import axios from 'axios'
-import 'leaflet/dist/leaflet.css'
-
-interface Opportunity {
- state: string
- municipality: string
- latitude: number
- longitude: number
- topic: string
- urgency: string
- confidence: number
- meeting_date: string
-}
-
-const urgencyColors: Record = {
- critical: '#dc2626',
- high: '#f97316',
- medium: '#fbbf24',
- low: '#22c55e',
-}
-
-export default function Heatmap() {
- const [selectedState, setSelectedState] = useState(null)
- const [selectedTopic, setSelectedTopic] = useState(null)
-
- const { data: opportunities, isLoading } = useQuery({
- queryKey: ['opportunities', selectedState, selectedTopic],
- queryFn: async () => {
- const params = new URLSearchParams()
- if (selectedState) params.append('state', selectedState)
- if (selectedTopic) params.append('topic', selectedTopic)
-
- const response = await axios.get(`/api/opportunities?${params}`)
- return response.data.opportunities || []
- },
- })
-
- if (isLoading) {
- return Loading map...
- }
-
- return (
-
- {/* Filters */}
-
-
-
- Filter by State
-
- setSelectedState(e.target.value || null)}
- >
- All States
- California
- Texas
- New York
- Florida
-
-
-
-
-
- Filter by Topic
-
- setSelectedTopic(e.target.value || null)}
- >
- All Topics
- Water Fluoridation
- School Dental Screening
- Medicaid Dental
-
-
-
-
- {/* Legend */}
-
-
Urgency Level
-
- {Object.entries(urgencyColors).map(([level, color]) => (
-
- ))}
-
-
-
- {/* Map */}
-
-
-
-
- {opportunities?.map((opp, idx) => (
-
-
-
-
{opp.municipality}, {opp.state}
-
- Topic: {opp.topic.replace(/_/g, ' ')}
-
-
- Urgency: {opp.urgency}
-
-
- Confidence: {(opp.confidence * 100).toFixed(0)}%
-
-
- Meeting Date: {new Date(opp.meeting_date).toLocaleDateString()}
-
-
-
-
- ))}
-
-
-
- {/* Summary */}
-
-
Summary
-
- Showing {opportunities?.length || 0} advocacy opportunities
- {selectedState && ` in ${selectedState}`}
- {selectedTopic && ` for ${selectedTopic.replace(/_/g, ' ')}`}
-
-
-
- )
-}
diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx
deleted file mode 100644
index 37eccbb1e270138d447da08845da48bef4fb44af..0000000000000000000000000000000000000000
--- a/frontend/src/pages/Home.tsx
+++ /dev/null
@@ -1,590 +0,0 @@
-import { Link, useNavigate, useSearchParams } from 'react-router-dom'
-import { useState, Fragment, useEffect } from 'react'
-import { Tab } from '@headlessui/react'
-import { useQuery } from '@tanstack/react-query'
-import axios from 'axios'
-import {
- MagnifyingGlassIcon,
- DocumentTextIcon,
- ChartBarIcon,
- BuildingLibraryIcon,
- ArrowRightIcon,
- BookOpenIcon,
- CurrencyDollarIcon,
- HomeIcon,
- TruckIcon,
- HeartIcon,
- AcademicCapIcon,
- BriefcaseIcon,
- ScaleIcon,
- UserGroupIcon,
- CodeBracketIcon,
- BuildingOfficeIcon,
- UserIcon,
- CheckCircleIcon,
- MapPinIcon
-} from '@heroicons/react/24/outline'
-import AddressLookup from '../components/AddressLookup'
-import { useLocation as useLocationContext } from '../contexts/LocationContext'
-
-export default function Home() {
- const navigate = useNavigate()
- const [searchParams] = useSearchParams()
- const [keyword, setKeyword] = useState('')
- const [searchScope, setSearchScope] = useState('city') // city, county, state, community (school), national
- const [selectedTab, setSelectedTab] = useState(0)
- const [showSuggestions, setShowSuggestions] = useState(false)
- const { location, setLocation } = useLocationContext()
-
- const DOCS_URL = import.meta.env.PROD ? '/docs/intro' : 'http://localhost:3000/docs/intro'
-
- // Live search preview (type-ahead with actual results from API)
- const { data: previewResults, isLoading: previewLoading, error: previewError } = useQuery({
- queryKey: ['search-preview-home', keyword, location?.state],
- queryFn: async () => {
- console.log('🔍 [Home] Fetching preview for:', keyword, 'in state:', location?.state);
- if (!keyword || keyword.length < 2) {
- console.log('⚠️ [Home] Query too short, skipping');
- return null;
- }
-
- const url = '/api/search/';
- const params: any = {
- q: keyword,
- types: 'causes,contacts,organizations',
- limit: 3
- };
-
- // Add state filter if location is set
- if (location && location.state) {
- params.state = location.state;
- console.log('📍 [Home] Filtering by state:', location.state);
- }
-
- console.log('📤 [Home] API Request:', url, params);
- const response = await axios.get(url, { params });
- console.log('📥 [Home] API Response:', response.data);
- console.log('📊 [Home] Total results:', response.data.total_results);
- return response.data;
- },
- enabled: keyword.length >= 2 && showSuggestions,
- staleTime: 1000
- });
-
- // Log when preview results change
- useEffect(() => {
- console.log('🔄 [Home] Preview results updated:', {
- hasResults: !!previewResults,
- totalResults: previewResults?.total_results,
- showSuggestions,
- keyword,
- isLoading: previewLoading,
- error: previewError
- });
- }, [previewResults, showSuggestions, keyword, previewLoading, previewError]);
-
- // Handle tab parameter from URL
- useEffect(() => {
- const tabParam = searchParams.get('tab')
- if (tabParam === 'community') {
- setSelectedTab(1) // Switch to "Find My Local Community" tab
- }
- }, [searchParams])
-
- // When location is set, default to city search if currently on 'community' (placeholder)
- useEffect(() => {
- if (location && searchScope === 'community') {
- setSearchScope('city')
- }
- }, [location, searchScope])
-
- const handleKeywordChange = (e: React.ChangeEvent) => {
- const value = e.target.value
- setKeyword(value)
- setShowSuggestions(value.length >= 2)
- }
-
- const handleSelectSuggestion = (suggestion: string) => {
- setKeyword(suggestion)
- setShowSuggestions(false)
- }
-
- const handleViewAllCategory = (category: string) => {
- if (keyword.trim().length >= 2) {
- const params = new URLSearchParams()
- params.set('q', keyword)
- params.set('types', category)
- if (location && location.state) {
- params.set('state', location.state)
- }
- navigate(`/search?${params.toString()}`)
- }
- }
-
- const handleSearch = (e: React.FormEvent) => {
- e.preventDefault()
- if (keyword || location) {
- const params = new URLSearchParams()
- if (keyword) params.set('search', keyword)
- if (searchScope) params.set('scope', searchScope)
-
- // Add location context based on scope
- if (location) {
- if (searchScope === 'state' || searchScope === 'county' || searchScope === 'city' || searchScope === 'community') {
- params.set('state', location.state)
- }
- if (searchScope === 'county' || searchScope === 'city' || searchScope === 'community') {
- if (location.county) params.set('county', location.county)
- }
- if (searchScope === 'city' || searchScope === 'community') {
- params.set('city', location.city)
- }
- }
-
- navigate(`/documents?${params.toString()}`)
- }
- }
-
- const handleAddressFound = (locationData: any) => {
- setLocation({
- address: locationData.address,
- state: locationData.state,
- county: locationData.county,
- city: locationData.city,
- latitude: locationData.latitude,
- longitude: locationData.longitude,
- })
- }
-
- const categories = [
- { name: 'People', icon: UserGroupIcon, query: '', route: '/people' },
- { name: 'Community', icon: CodeBracketIcon, query: 'community engagement' },
- { name: 'Budget', icon: CurrencyDollarIcon, query: 'budget funding' },
- { name: 'Housing', icon: HomeIcon, query: 'housing affordable' },
- { name: 'Transport', icon: TruckIcon, query: 'transportation transit' },
- { name: 'Health', icon: HeartIcon, query: 'health dental' },
- { name: 'Education', icon: AcademicCapIcon, query: 'education school' },
- { name: 'Jobs', icon: BriefcaseIcon, query: 'employment jobs' },
- { name: 'Legal', icon: ScaleIcon, query: 'legal services' },
- { name: 'Charities', icon: BuildingLibraryIcon, query: '', route: '/nonprofits' },
- ]
-
- const quickSearch = (category: { query: string, route?: string }) => {
- if (category.route) {
- if (category.route.startsWith('http')) {
- window.open(category.route, '_blank')
- } else {
- navigate(category.route)
- }
- } else if (category.query) {
- navigate(`/search?q=${encodeURIComponent(category.query)}`)
- }
- }
-
- return (
-
-
-
-
- Open Navigator for Engagement
-
-
- Track what local governments and charities say, spend—and block.
-
- Find leaders by name. Discover causes.{' '}
- 925 jurisdictions.{' '}
- 43,726 nonprofits. All free.
-
-
- {/* Tabbed Interface */}
-
-
-
-
- {({ selected }) => (
-
- 🔍 Search Topics
-
- )}
-
-
- {({ selected }) => (
-
- 📍 Find My Local Community
-
- )}
-
-
-
-
- {/* Search Tab */}
-
-
-
-
- {/* Find My Community Tab */}
-
-
-
- What's Happening in Your Community?
-
-
- Enter your address to find local organizations, city councils, county boards, school districts, and charities near you
-
-
-
- {/* Success message and return to search button */}
- {location && (
-
-
-
-
-
- Location Set Successfully!
-
-
- You're all set for {location.city}, {location.state} . Now you can search for topics in your community.
-
-
-
-
setSelectedTab(0)}
- className="w-full bg-green-600 hover:bg-green-700 text-white px-6 py-4 rounded-lg font-semibold text-lg flex items-center justify-center gap-2 transition-all shadow-lg hover:shadow-xl"
- >
-
- Search Topics in My Community
-
-
-
- )}
-
-
-
-
-
-
- {/* Quick Topics */}
-
-
Popular topics:
-
- {categories.map((category) => (
- quickSearch(category)}
- className="inline-flex items-center gap-2 px-4 py-2 bg-white border-2 border-gray-300 rounded-lg hover:border-primary-500 hover:bg-primary-50 transition-colors"
- >
-
- {category.name}
-
- ))}
-
-
-
- {/* Terms */}
-
- By continuing, you agree to the{' '}
- Terms
- {' & '}
- Privacy .
-
-
-
-
- {/* Features Grid */}
-
-
-
Explore the Platform
-
-
-
-
-
- {/* Stats Section */}
-
-
-
-
-
2,500+
-
Causes Tracked
-
-
-
15,000+
-
Government Decisions
-
-
-
8 Years
-
Historical Coverage
-
-
-
5,000+
-
Meeting Records
-
-
-
12,000+
-
Hours of Video
-
-
-
-
-
-
- )
-}
diff --git a/frontend/src/pages/HomeModern.tsx b/frontend/src/pages/HomeModern.tsx
deleted file mode 100644
index 3c975df377bd8a330c062038210336e06442480c..0000000000000000000000000000000000000000
--- a/frontend/src/pages/HomeModern.tsx
+++ /dev/null
@@ -1,1326 +0,0 @@
-import { Link, useNavigate, useSearchParams } from 'react-router-dom'
-import { useState, useEffect, Fragment } from 'react'
-import { Tab } from '@headlessui/react'
-import { useQuery } from '@tanstack/react-query'
-import axios from 'axios'
-import {
- MagnifyingGlassIcon,
- DocumentTextIcon,
- ChartBarIcon,
- BuildingLibraryIcon,
- UserGroupIcon,
- MapIcon,
- BellAlertIcon,
- SparklesIcon,
- CheckCircleIcon,
- RocketLaunchIcon,
- ArrowRightIcon,
- HeartIcon,
- BookOpenIcon,
- CodeBracketIcon,
- AcademicCapIcon,
- CommandLineIcon,
- Bars3Icon,
- XMarkIcon,
- BuildingOfficeIcon,
- UserIcon,
- EnvelopeIcon
-} from '@heroicons/react/24/outline'
-import { useLocation as useLocationContext } from '../contexts/LocationContext'
-import AddressLookup from '../components/AddressLookup'
-
-export default function HomeModern() {
- const navigate = useNavigate()
- const [searchParams] = useSearchParams()
- const [keyword, setKeyword] = useState('')
- const [activeSection, setActiveSection] = useState('hero')
- const [selectedTab, setSelectedTab] = useState(0)
- const [searchScope, setSearchScope] = useState('city')
- const [showSuggestions, setShowSuggestions] = useState(false)
- const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
- const { location, setLocation} = useLocationContext()
-
- // Environment-aware URLs for docs and API
- // In development: Docusaurus on localhost:3000/docs, API on localhost:8000
- // In production (HF Spaces): Served via nginx at /docs/ and /api/
- const docsBaseUrl = import.meta.env.VITE_DOCS_URL ||
- (import.meta.env.DEV ? 'http://localhost:3000/docs' : '/docs')
- const apiBaseUrl = import.meta.env.VITE_API_URL ||
- (import.meta.env.DEV ? 'http://localhost:8000' : '')
-
- // Fetch real stats from API - updates based on selected location
- const { data: statsData } = useQuery({
- queryKey: ['platform-stats', location?.state, location?.county, location?.city],
- queryFn: async () => {
- const params: any = {};
- if (location && location.state) {
- params.state = location.state;
- }
- if (location && location.county) {
- params.county = location.county;
- }
- if (location && location.city) {
- params.city = location.city;
- }
- const response = await axios.get('/api/stats', { params });
- return response.data.data;
- },
- staleTime: 1000 * 60 * 60, // Cache for 1 hour
- refetchOnWindowFocus: false
- })
-
- // Live search preview (type-ahead with actual results from API)
- const { data: previewResults, isLoading: previewLoading, error: previewError } = useQuery({
- queryKey: ['search-preview-home', keyword, location?.state],
- queryFn: async () => {
- console.log('🔍 [HomeModern] Fetching preview for:', keyword, 'in state:', location?.state);
- if (!keyword || keyword.length < 2) {
- console.log('⚠️ [HomeModern] Query too short, skipping');
- return null;
- }
-
- const url = '/api/search/';
- const params: any = {
- q: keyword,
- types: 'causes,contacts,organizations',
- limit: 3
- };
-
- // Add state filter if location is set
- if (location && location.state) {
- params.state = location.state;
- console.log('📍 [HomeModern] Filtering by state:', location.state);
- }
-
- console.log('📤 [HomeModern] API Request:', url, params);
- const response = await axios.get(url, { params });
- console.log('📥 [HomeModern] API Response:', response.data);
- console.log('📊 [HomeModern] Total results:', response.data.total_results);
- console.log('🎯 [HomeModern] Causes:', response.data.results.causes.length);
- console.log('👥 [HomeModern] Contacts:', response.data.results.contacts.length);
- console.log('🏢 [HomeModern] Organizations:', response.data.results.organizations.length);
- return response.data;
- },
- enabled: keyword.length >= 2 && showSuggestions,
- staleTime: 1000 // Cache for 1 second to avoid excessive requests
- });
-
- // Log when preview results change
- useEffect(() => {
- console.log('🔄 [HomeModern] Preview results updated:', {
- hasResults: !!previewResults,
- totalResults: previewResults?.total_results,
- showSuggestions,
- keyword,
- isLoading: previewLoading,
- error: previewError
- });
- }, [previewResults, showSuggestions, keyword, previewLoading, previewError]);
-
- // Handle tab parameter from URL
- useEffect(() => {
- const tabParam = searchParams.get('tab')
- if (tabParam === 'community') {
- setSelectedTab(1)
- }
- }, [searchParams])
-
- // Handle scope parameter from URL (when navigating from jurisdiction cards)
- useEffect(() => {
- const scopeParam = searchParams.get('scope')
- if (scopeParam && ['city', 'county', 'state', 'community', 'national'].includes(scopeParam)) {
- setSearchScope(scopeParam)
- setSelectedTab(0) // Switch to search tab
- }
- }, [searchParams])
-
- // When location is set, default to city search
- useEffect(() => {
- if (location && searchScope === 'community') {
- setSearchScope('city')
- }
- }, [location, searchScope])
-
- const handleKeywordChange = (e: React.ChangeEvent) => {
- const value = e.target.value;
- console.log('⌨️ [HomeModern] Keyword changed:', value);
- setKeyword(value);
- setShowSuggestions(value.length >= 2);
- console.log('👁️ [HomeModern] Show suggestions:', value.length >= 2);
- }
-
- const handleSelectSuggestion = (suggestion: string) => {
- setKeyword(suggestion)
- setShowSuggestions(false)
-
- // Navigate to search results with the selected suggestion
- const params = new URLSearchParams()
- params.set('q', suggestion)
- if (location && location.state) {
- params.set('state', location.state)
- }
- navigate(`/search?${params.toString()}`)
- }
-
- const handleViewAllCategory = (category: string) => {
- if (keyword.trim().length >= 2) {
- const params = new URLSearchParams()
- params.set('q', keyword)
- params.set('types', category)
- if (location && location.state) {
- params.set('state', location.state)
- }
- navigate(`/search?${params.toString()}`)
- }
- }
-
- // Smooth scroll to section
- const scrollToSection = (sectionId: string) => {
- const element = document.getElementById(sectionId)
- if (element) {
- const offset = 80 // Account for sticky header
- const elementPosition = element.getBoundingClientRect().top
- const offsetPosition = elementPosition + window.pageYOffset - offset
-
- window.scrollTo({
- top: offsetPosition,
- behavior: 'smooth'
- })
- }
- }
-
- // Track active section on scroll
- useEffect(() => {
- const handleScroll = () => {
- const sections = ['hero', 'features', 'how-it-works', 'stats', 'get-started']
- const scrollPosition = window.scrollY + 100
-
- for (const section of sections) {
- const element = document.getElementById(section)
- if (element) {
- const { offsetTop, offsetHeight } = element
- if (scrollPosition >= offsetTop && scrollPosition < offsetTop + offsetHeight) {
- setActiveSection(section)
- break
- }
- }
- }
- }
-
- window.addEventListener('scroll', handleScroll)
- return () => window.removeEventListener('scroll', handleScroll)
- }, [])
-
- const handleSearch = (e: React.FormEvent) => {
- e.preventDefault()
- if (keyword || location) {
- const params = new URLSearchParams()
- if (keyword) params.set('q', keyword)
-
- // Add location context
- if (location && location.state) {
- params.set('state', location.state)
- }
-
- navigate(`/search?${params.toString()}`)
- }
- }
-
- const handleAddressFound = (locationData: any) => {
- setLocation({
- address: locationData.address,
- state: locationData.state,
- county: locationData.county,
- city: locationData.city,
- latitude: locationData.latitude,
- longitude: locationData.longitude,
- })
- }
-
- const navLinks = [
- { id: 'hero', label: 'Home' },
- { id: 'features', label: 'Features' },
- { id: 'how-it-works', label: 'How It Works' },
- { id: 'stats', label: 'Impact' },
- { id: 'get-started', label: 'Documentation' },
- { id: 'contact', label: 'Contact' },
- ]
-
- const features = [
- {
- icon: DocumentTextIcon,
- title: 'Policy Decisions',
- description: 'Track 500K+ meeting pages with decision analysis, deferral patterns, and stakeholder positions',
- link: '/documents',
- color: '#354F52'
- },
- {
- icon: ChartBarIcon,
- title: 'Budget Analysis',
- description: 'Compare budget rhetoric to reality with $2T+ in tracked spending and delta analysis',
- link: '/analytics',
- color: '#52796F'
- },
- {
- icon: UserGroupIcon,
- title: 'Elected Officials',
- description: 'Follow 362 officials across 925 jurisdictions with voting records and decision patterns',
- link: '/people',
- color: '#84A98C'
- },
- {
- icon: MapIcon,
- title: 'Demographics & Data',
- description: 'Census demographics, income, education, housing, and health data for every jurisdiction',
- link: '/heatmap',
- color: '#4A90E2'
- },
- {
- icon: BuildingLibraryIcon,
- title: 'Nonprofits & Churches',
- description: '43,726 nonprofits including 4,372 churches with financial data from 5 states',
- link: '/nonprofits',
- color: '#9B59B6'
- },
- {
- icon: BellAlertIcon,
- title: 'Fact-Checking',
- description: 'Verify claims with integrated PolitiFact, FactCheck.org, and Google Fact Check data',
- link: '/debate-grader',
- color: '#E74C3C'
- },
- ]
-
- return (
-
- {/* Sticky Navigation Header */}
-
-
-
- {/* Logo */}
-
-
-
- Open Navigator
-
-
-
- {/* Desktop Navigation Links */}
-
- {navLinks.map((link) => (
- scrollToSection(link.id)}
- className={`text-sm font-medium transition-colors ${
- activeSection === link.id
- ? 'text-[#354F52] border-b-2 border-[#354F52]'
- : 'text-gray-600 hover:text-[#354F52]'
- } pb-1`}
- >
- {link.label}
-
- ))}
-
-
- {/* Desktop CTA Button */}
-
- Explore Now
-
-
- {/* Mobile Menu Button */}
-
setMobileMenuOpen(!mobileMenuOpen)}
- className="md:hidden p-2 rounded-lg text-gray-600 hover:bg-gray-100 transition-colors"
- aria-label="Toggle menu"
- >
- {mobileMenuOpen ? (
-
- ) : (
-
- )}
-
-
-
-
- {/* Mobile Menu */}
- {mobileMenuOpen && (
-
-
- {navLinks.map((link) => (
- {
- scrollToSection(link.id)
- setMobileMenuOpen(false)
- }}
- className={`block w-full text-left px-4 py-3 rounded-lg text-base font-medium transition-colors ${
- activeSection === link.id
- ? 'bg-[#354F52] text-white'
- : 'text-gray-700 hover:bg-gray-100'
- }`}
- >
- {link.label}
-
- ))}
- setMobileMenuOpen(false)}
- >
- Explore Now
-
-
-
- )}
-
-
- {/* Hero Section */}
-
-
-
-
-
- Local Community Impact Made Easy
-
-
-
-
- Track Local Decisions.
-
- Take Action.
-
-
-
-
- Follow leaders, charities, and causes in your community.
- {/* Show search-filtered counts when actively searching, otherwise show location stats */}
- {keyword.length >= 2 && previewResults && previewResults.total_results > 0 ? (
- <>
-
- {previewResults.total_results.toLocaleString()} result{previewResults.total_results !== 1 ? 's' : ''}
-
- {' matching "'}
- {keyword}
- {'"'}
- {location && (
- <>
- {' in '}
-
- {location.city || location.county || location.state}
-
- >
- )}
- >
- ) : keyword.length >= 2 && previewResults && previewResults.total_results === 0 ? (
- <>
-
- No results found for "{keyword}"
- {location && ` in ${location.city || location.county || location.state}`}
-
- >
- ) : statsData ? (
- statsData.level === 'state' ? (
- <>
-
- {statsData.jurisdictions_display} jurisdictions
-
- {' • '}
-
- {statsData.nonprofits_display} nonprofits
-
- {' • '}
-
- {statsData.contacts_display} leaders
-
- {' • '}
-
- {statsData.causes_display} causes
-
- {' in '}{statsData.location} • 100% free
- >
- ) : statsData.level === 'city' && statsData.jurisdictions_breakdown ? (
- <>
-
- {statsData.jurisdictions_display} jurisdictions
-
- {' • '}
-
- {statsData.nonprofits_display} nonprofits
-
- {' • '}
-
- {statsData.contacts_display} leaders
-
- {' • '}
-
- {statsData.causes_display} causes
-
- {' in '}{statsData.location} • 100% free
- >
- ) : (
- <>
-
- {statsData.jurisdictions_display} jurisdictions
-
- {' • '}
-
- {statsData.nonprofits_display} nonprofits
-
- {' • '}
-
- {statsData.contacts_display} leaders
-
- {' • '}
-
- {statsData.causes_display} causes
-
- {' • 100% free'}
- >
- )
- ) : (
- <>
-
- 925 jurisdictions
-
- {' • '}
-
- 43,726 nonprofits
-
- {' • '}
-
- 10,000+ leaders
-
- {' • '}
-
- 650+ causes
-
- {' • 100% free'}
- >
- )}
-
-
- {/* Tabbed Search Interface */}
-
-
-
-
- {({ selected }) => (
-
- 🔍 Search Topics
-
- )}
-
-
- {({ selected }) => (
-
- 📍 Find My Community
-
- )}
-
-
-
-
- {/* Search Topics Tab */}
-
-
-
-
- {/* Find My Community Tab */}
-
-
-
- What's Happening in Your Community?
-
-
- Enter your address to find local organizations, city councils, county boards, school districts, and charities near you
-
-
-
- {/* Success message and return to search button */}
- {location && (
-
-
-
-
-
- Location Set Successfully!
-
-
- You're all set for {location.city}, {location.state} . Now you can search for topics in your community.
-
-
-
-
setSelectedTab(0)}
- className="w-full bg-green-600 hover:bg-green-700 text-white px-6 py-4 rounded-lg font-semibold text-lg flex items-center justify-center gap-2 transition-all shadow-lg hover:shadow-xl"
- >
-
- Search Topics in My Community
-
-
-
- )}
-
-
-
-
-
-
- {/* Quick Stats */}
-
-
-
- {statsData?.jurisdictions_display || '90,000+'} Jurisdictions
-
-
-
- {statsData?.nonprofits_display || '43,726'} Nonprofits
-
-
-
- {statsData?.meetings_display || '500K+'} Meetings Analyzed
-
-
-
-
-
- {/* Jurisdiction Discovery CTA */}
-
-
-
-
-
- Explore Jurisdictions
-
-
- Search and filter through 32,000+ cities, counties, and school districts across all 50 states.
- View meeting schedules, discovery status, and available data sources.
-
-
-
-
-
-
- {/* Features Section */}
-
-
-
-
- Everything You Need
-
-
- Powerful tools to stay informed and engaged with the most impactful details
-
-
-
-
- {features.map((feature, index) => (
-
-
-
-
-
- {feature.title}
-
-
- {feature.description}
-
-
- Learn more
-
-
- ))}
-
-
-
-
- {/* How It Works */}
-
-
-
-
- How It Works
-
-
- Get started in three simple steps
-
-
-
-
- {[
- {
- step: '1',
- title: 'Set Your Location',
- description: 'Tell us where you live to see local leaders, meetings, and charities',
- icon: MapIcon
- },
- {
- step: '2',
- title: 'Follow What Matters',
- description: 'Follow leaders, organizations, and causes you care about',
- icon: HeartIcon
- },
- {
- step: '3',
- title: 'Stay Informed',
- description: 'Get updates on local decisions and opportunities to take action',
- icon: BellAlertIcon
- },
- ].map((item, index) => (
-
-
- {item.step}
-
-
-
- {item.title}
-
-
- {item.description}
-
-
- ))}
-
-
-
-
- {/* Stats Section */}
-
-
-
-
- {statsData?.level === 'state' ? `Our Impact in ${statsData.location}` : 'Our Impact'}
-
-
- {statsData?.level === 'state' ?
- `Real numbers for ${statsData.location} from live data tables` :
- `Real numbers from real data tables`
- }
- {statsData?.last_updated && (
-
- (updated {new Date(statsData.last_updated).toLocaleDateString()})
-
- )}
-
-
-
-
- {(() => {
- const stats = [
- {
- value: statsData?.jurisdictions_display || '925',
- label: 'Jurisdictions Tracked',
- description: 'Cities, counties, states, and tribal governments',
- color: '#354F52'
- },
- {
- value: statsData?.nonprofits_display || '43,726',
- label: 'Nonprofits & Churches',
- description: statsData ? `${statsData.states_with_data} states with full IRS BMF data` : '5 states with full IRS BMF data',
- color: '#52796F'
- },
- {
- value: statsData?.meetings_display || '6,913',
- label: 'Meeting Pages Analyzed',
- description: 'AI-extracted decisions and budget items',
- color: '#84A98C'
- },
- {
- value: statsData?.budget_tracked || 'N/A',
- label: 'Budget Dollars',
- description: 'Real-time tracking and delta analysis',
- color: '#4A90E2'
- },
- {
- value: statsData?.contacts_display || '362',
- label: 'Elected Officials',
- description: 'Voting records and decision patterns',
- color: '#9B59B6'
- },
- {
- value: statsData?.churches || '4,372',
- label: 'Churches & Congregations',
- description: 'Community-based organizations mapped',
- color: '#6B8E23'
- },
- {
- value: statsData?.policy_decisions || 'N/A',
- label: 'Policy Decisions',
- description: 'With deferral tracking and stakeholder positions',
- color: '#DC143C'
- },
- {
- value: statsData?.school_districts_display || '306',
- label: 'School Districts',
- description: 'NCES-validated educational boundaries',
- color: '#8B4513'
- },
- {
- value: statsData?.states_with_data ? `${statsData.states_with_data} State${statsData.states_with_data !== 1 ? 's' : ''}` : '5 States',
- label: 'State Coverage',
- description: 'Including territories and tribal nations',
- color: '#2E8B57'
- },
- {
- value: statsData?.grant_opportunities || '1,000s',
- label: 'Grant Opportunities',
- description: 'Federal, state, and foundation funding',
- color: '#FF6B6B'
- },
- {
- value: statsData?.fact_checks || 'N/A',
- label: 'Fact-Checked Claims',
- description: 'PolitiFact, FactCheck.org integration',
- color: '#4ECDC4'
- },
- {
- value: '100%',
- label: 'Free & Open Source',
- description: 'MIT License, HuggingFace datasets',
- color: '#95E1D3'
- },
- ];
-
- return stats.map((stat, index) => (
-
-
- {stat.value}
-
-
- {stat.label}
-
-
- {stat.description}
-
-
- ));
- })()}
-
-
-
-
- {/* Documentation Section */}
-
-
-
-
-
- Documentation & Resources
-
-
- Choose your path based on your role and technical expertise
-
-
-
- {/* Documentation Tiles */}
-
- {/* Getting Started - Everyone */}
-
-
-
- Getting Started
-
-
- New here? Start with our quick introduction and dashboard overview.
-
-
- For Everyone →
-
-
-
- {/* Families & Individuals */}
-
-
-
- Families & Individuals
-
-
- Community events, voter registration, services, and how to engage locally.
-
-
- Community Resources →
-
-
-
- {/* Policy Makers & Advocates - Non-Technical */}
-
-
-
- Policy Makers
-
-
- Case studies, data insights, and how to use the platform for advocacy.
-
-
- Non-Technical →
-
-
-
- {/* Developers & Technical Users */}
-
-
-
- Developers
-
-
- Setup guides, API docs, deployment instructions, and integrations.
-
-
- Technical →
-
-
-
- {/* Full Documentation */}
-
-
-
- Full Docs
-
-
- Complete documentation including data sources, guides, and more.
-
-
- Browse All →
-
-
-
-
- {/* Quick Actions Below */}
-
-
- Browse Leaders →
-
-
- Explore Charities →
-
-
-
-
-
- {/* Contact Us CTA */}
-
-
- {/* Footer */}
-
-
- )
-}
diff --git a/frontend/src/pages/JurisdictionsSearch.tsx b/frontend/src/pages/JurisdictionsSearch.tsx
deleted file mode 100644
index 1f347e0bd9e631a076649f70c7a1dcccf79d85c2..0000000000000000000000000000000000000000
--- a/frontend/src/pages/JurisdictionsSearch.tsx
+++ /dev/null
@@ -1,663 +0,0 @@
-import { useState, useRef, useEffect, Fragment } from 'react'
-import { useSearchParams, Link } from 'react-router-dom'
-import { useQuery } from '@tanstack/react-query'
-import axios from 'axios'
-import { Menu, Transition } from '@headlessui/react'
-import {
- MagnifyingGlassIcon,
- XMarkIcon,
- AdjustmentsHorizontalIcon,
- CheckIcon,
- MapPinIcon,
- ChevronDownIcon,
- Cog6ToothIcon,
- ArrowRightOnRectangleIcon
-} from '@heroicons/react/24/outline'
-import { useAuth } from '../contexts/AuthContext'
-import JurisdictionDiscovery from '../components/JurisdictionDiscovery'
-
-interface JurisdictionResult {
- type: 'jurisdiction'
- title: string
- subtitle: string
- description: string
- url: string
- score: number
- metadata: {
- level?: string
- state?: string
- website?: string
- youtube_channels?: string[]
- facebook?: string
- twitter?: string
- agenda_portal?: string
- meeting_platform?: string
- completeness?: number
- }
-}
-
-interface SearchResponse {
- query: string
- total_results: number
- results: {
- jurisdictions: JurisdictionResult[]
- }
- pagination: {
- page: number
- limit: number
- offset: number
- total_pages: number
- has_next: boolean
- has_prev: boolean
- }
- filters: {
- state?: string
- jurisdiction_levels?: string[]
- }
-}
-
-const JURISDICTION_LEVELS = [
- { id: 'city', label: 'Cities', icon: '🏙️' },
- { id: 'county', label: 'Counties', icon: '🏛️' },
- { id: 'state', label: 'States', icon: '🗺️' },
- { id: 'school_district', label: 'School Districts', icon: '🎓' },
- { id: 'special_district', label: 'Special Districts', icon: '⚙️' },
- { id: 'town', label: 'Towns', icon: '🏘️' },
- { id: 'village', label: 'Villages', icon: '🏡' },
-] as const
-
-export default function JurisdictionsSearch() {
- const [searchParams, setSearchParams] = useSearchParams()
-
- // Initialize state from URL params
- const [query, setQuery] = useState(() => searchParams.get('q') || '')
- const [activeQuery, setActiveQuery] = useState(() => searchParams.get('q') || '')
- const [selectedLevels, setSelectedLevels] = useState(() => {
- const levelsParam = searchParams.get('levels')
- if (levelsParam) {
- return levelsParam.split(',').filter(l =>
- JURISDICTION_LEVELS.some(jl => jl.id === l)
- )
- }
- return []
- })
- const [selectedState, setSelectedState] = useState(() => searchParams.get('state') || '')
- const [currentPage, setCurrentPage] = useState(() => parseInt(searchParams.get('page') || '1'))
- const [showFilters, setShowFilters] = useState(false)
-
- const searchInputRef = useRef(null)
- const { user, isAuthenticated, login, logout, isLoading } = useAuth()
-
- // Initialize from URL parameters on mount
- useEffect(() => {
- const queryParam = searchParams.get('q')
- const stateParam = searchParams.get('state')
- const levelsParam = searchParams.get('levels')
- const pageParam = searchParams.get('page')
-
- if (queryParam) {
- setQuery(queryParam)
- setActiveQuery(queryParam)
- }
- if (stateParam) {
- setSelectedState(stateParam)
- }
- if (levelsParam) {
- const levels = levelsParam.split(',').filter(l =>
- JURISDICTION_LEVELS.some(jl => jl.id === l)
- )
- if (levels.length > 0) {
- setSelectedLevels(levels)
- }
- }
- if (pageParam) {
- setCurrentPage(parseInt(pageParam))
- }
- }, [searchParams])
-
- // Main search results
- const { data: searchResults, isLoading: isSearching, error } = useQuery({
- queryKey: ['jurisdictions-search', activeQuery, selectedLevels, selectedState, currentPage],
- queryFn: async () => {
- // Allow searching with query OR with filters (browse mode)
- if (!activeQuery && !selectedState && !selectedLevels.length) {
- return null
- }
-
- const params: any = {
- types: 'jurisdictions',
- limit: 20,
- page: currentPage
- }
-
- // Query is optional - can browse by state/level
- if (activeQuery) {
- params.q = activeQuery
- }
-
- if (selectedState) {
- params.state = selectedState
- }
-
- if (selectedLevels.length > 0) {
- params.jurisdiction_levels = selectedLevels.join(',')
- }
-
- const response = await axios.get('/api/search/', { params })
- return response.data
- },
- // Enable if we have query OR filters (browse mode)
- enabled: (activeQuery && activeQuery.length >= 2) || selectedState !== '' || selectedLevels.length > 0
- })
-
- const handleSearch = (e?: React.FormEvent) => {
- e?.preventDefault()
- // Allow search with query OR just filters (browse mode)
- if (query.trim().length >= 2 || selectedState || selectedLevels.length > 0) {
- setActiveQuery(query)
- setCurrentPage(1) // Reset to first page on new search
-
- // Update URL
- const params: any = {}
- if (query.trim()) params.q = query
- if (selectedState) params.state = selectedState
- if (selectedLevels.length > 0) {
- params.levels = selectedLevels.join(',')
- }
- setSearchParams(params)
- }
- }
-
- const handlePageChange = (newPage: number) => {
- setCurrentPage(newPage)
-
- // Update URL
- const params: any = {}
- if (activeQuery) params.q = activeQuery
- if (selectedState) params.state = selectedState
- if (selectedLevels.length > 0) {
- params.levels = selectedLevels.join(',')
- }
- if (newPage > 1) params.page = newPage.toString()
- setSearchParams(params)
-
- // Scroll to top
- window.scrollTo({ top: 0, behavior: 'smooth' })
- }
-
- const toggleLevel = (level: string) => {
- const newLevels = selectedLevels.includes(level)
- ? selectedLevels.filter(l => l !== level)
- : [...selectedLevels, level]
-
- setSelectedLevels(newLevels)
- setCurrentPage(1)
-
- // Update URL with all current filters
- const params: any = {}
- if (activeQuery) params.q = activeQuery
- if (selectedState) params.state = selectedState
- if (newLevels.length > 0) {
- params.levels = newLevels.join(',')
- }
- setSearchParams(params)
- }
-
- const getLevelColor = (level: string) => {
- const colors: Record = {
- city: 'bg-blue-100 text-blue-700 border-blue-200',
- county: 'bg-purple-100 text-purple-700 border-purple-200',
- state: 'bg-green-100 text-green-700 border-green-200',
- school_district: 'bg-yellow-100 text-yellow-700 border-yellow-200',
- special_district: 'bg-orange-100 text-orange-700 border-orange-200',
- town: 'bg-teal-100 text-teal-700 border-teal-200',
- village: 'bg-pink-100 text-pink-700 border-pink-200',
- }
- return colors[level] || 'bg-gray-100 text-gray-700 border-gray-200'
- }
-
- return (
-
- {/* Top Navigation Bar */}
-
-
-
-
-
- Open Navigator
-
-
-
- {/* Login/Avatar */}
-
- {isLoading ? (
-
- ) : isAuthenticated && user ? (
-
-
- {user.avatar_url ? (
- {
- e.currentTarget.style.display = 'none';
- const fallback = e.currentTarget.nextElementSibling as HTMLElement | null;
- if (fallback) fallback.style.display = 'flex';
- }}
- />
- ) : null}
-
- {(user.full_name || user.username || user.email).charAt(0).toUpperCase()}
-
-
- {user.full_name || user.username || user.email.split('@')[0]}
-
-
-
-
-
-
-
-
{user.full_name || user.username}
-
{user.email}
-
-
-
- {({ active }) => (
-
-
- Settings
-
- )}
-
-
- {({ active }) => (
-
-
- Sign Out
-
- )}
-
-
-
-
-
- ) : (
-
login('huggingface')}
- className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors font-medium"
- >
- Login
-
- )}
-
-
-
-
-
- {/* Search Header */}
-
-
-
-
Jurisdiction Search
-
-
- Search across 90,000+ cities, counties, states, and school districts
-
-
- {/* Search Bar */}
-
-
- {/* Filter Bar */}
-
-
setShowFilters(!showFilters)}
- className={`flex items-center gap-2 px-4 py-2 rounded-lg border-2 transition-colors ${
- showFilters
- ? 'border-primary-500 bg-primary-50 text-primary-700'
- : 'border-gray-300 text-gray-700 hover:border-gray-400 hover:bg-gray-50'
- }`}
- >
-
- Filters
- {selectedState && (
-
- 1
-
- )}
-
-
- {/* Jurisdiction Level Pills */}
-
- Levels:
- {JURISDICTION_LEVELS.map((level) => (
- toggleLevel(level.id)}
- className={`flex items-center gap-2 px-4 py-2 rounded-full border-2 transition-all ${
- selectedLevels.includes(level.id)
- ? `${getLevelColor(level.id)} border-current font-medium shadow-sm`
- : 'border-gray-300 bg-white text-gray-600 hover:border-gray-400 hover:bg-gray-50'
- }`}
- >
- {selectedLevels.includes(level.id) && (
-
- )}
- {level.icon}
- {level.label}
-
- ))}
-
-
-
- {/* Active Filters Display */}
- {(selectedState || selectedLevels.length > 0) && (
-
- Active filters:
- {selectedState && (
-
- State: {selectedState}
- {
- setSelectedState('')
- setTimeout(() => handleSearch(), 0)
- }}
- className="hover:bg-blue-200 rounded-full p-0.5"
- >
-
-
-
- )}
- {selectedLevels.length > 0 && (
-
- {selectedLevels.length} Level{selectedLevels.length > 1 ? 's' : ''}
- {
- setSelectedLevels([])
- setTimeout(() => handleSearch(), 0)
- }}
- className="hover:bg-purple-200 rounded-full p-0.5"
- >
-
-
-
- )}
-
- )}
-
- {/* Advanced Filters Panel */}
- {showFilters && (
-
-
- {/* State Filter */}
-
-
- State
-
- {
- setSelectedState(e.target.value)
- setCurrentPage(1)
- setTimeout(() => handleSearch(), 0)
- }}
- className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 text-gray-900 bg-white"
- >
- All States
- Alabama
- Alaska
- Arizona
- Arkansas
- California
- Colorado
- Connecticut
- Delaware
- Florida
- Georgia
- Hawaii
- Idaho
- Illinois
- Indiana
- Iowa
- Kansas
- Kentucky
- Louisiana
- Maine
- Maryland
- Massachusetts
- Michigan
- Minnesota
- Mississippi
- Missouri
- Montana
- Nebraska
- Nevada
- New Hampshire
- New Jersey
- New Mexico
- New York
- North Carolina
- North Dakota
- Ohio
- Oklahoma
- Oregon
- Pennsylvania
- Rhode Island
- South Carolina
- South Dakota
- Tennessee
- Texas
- Utah
- Vermont
- Virginia
- Washington
- West Virginia
- Wisconsin
- Wyoming
-
-
-
-
- {/* Clear All Button */}
-
- {
- setSelectedState('')
- setSelectedLevels([])
- setTimeout(() => handleSearch(), 0)
- }}
- className="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 transition-colors"
- >
- Clear All Filters
-
-
-
- )}
-
-
- {/* Search Results */}
- {(activeQuery || selectedState || selectedLevels.length > 0 || searchResults) && (
-
- {isSearching && (
-
- )}
-
- {error && (
-
-
Error loading search results. Please try again.
-
- )}
-
- {searchResults && searchResults.total_results !== undefined && searchResults.pagination && (
- <>
- {/* Results Summary */}
-
-
- {searchResults.query ? (
- <>
- {searchResults.total_results.toLocaleString()} jurisdictions for "{searchResults.query}"
- {searchResults.total_results > 0 && (
-
- (showing {searchResults.pagination.offset + 1}-
- {Math.min(searchResults.pagination.offset + searchResults.pagination.limit, searchResults.total_results)})
-
- )}
- >
- ) : (
- <>
- {searchResults.total_results.toLocaleString()} jurisdictions
- {searchResults.total_results > 0 && (
-
- (showing {searchResults.pagination.offset + 1}-
- {Math.min(searchResults.pagination.offset + searchResults.pagination.limit, searchResults.total_results)})
-
- )}
- >
- )}
-
- {selectedState && (
-
- Filtered by state: {selectedState}
-
- )}
-
-
- {/* Results */}
-
- {searchResults.results.jurisdictions.map((result, index) => (
-
- ))}
-
-
- {/* No Results */}
- {searchResults.total_results === 0 && (
-
-
-
No jurisdictions found
-
- Try adjusting your search terms or filters
-
-
- )}
-
- {/* Pagination */}
- {searchResults.total_results > 0 && searchResults.pagination.total_pages > 1 && (
-
-
handlePageChange(currentPage - 1)}
- disabled={!searchResults.pagination.has_prev}
- className={`px-4 py-2 rounded-lg ${
- searchResults.pagination.has_prev
- ? 'bg-white border border-gray-300 text-gray-700 hover:bg-gray-50'
- : 'bg-gray-100 text-gray-400 cursor-not-allowed'
- }`}
- >
- Previous
-
-
-
- {Array.from({ length: Math.min(5, searchResults.pagination.total_pages) }, (_, i) => {
- const pageNum = i + 1
- return (
- handlePageChange(pageNum)}
- className={`px-4 py-2 rounded-lg ${
- currentPage === pageNum
- ? 'bg-primary-600 text-white'
- : 'bg-white border border-gray-300 text-gray-700 hover:bg-gray-50'
- }`}
- >
- {pageNum}
-
- )
- })}
-
-
-
handlePageChange(currentPage + 1)}
- disabled={!searchResults.pagination.has_next}
- className={`px-4 py-2 rounded-lg ${
- searchResults.pagination.has_next
- ? 'bg-white border border-gray-300 text-gray-700 hover:bg-gray-50'
- : 'bg-gray-100 text-gray-400 cursor-not-allowed'
- }`}
- >
- Next
-
-
- )}
- >
- )}
-
- )}
-
-
- )
-}
diff --git a/frontend/src/pages/Nonprofits.tsx b/frontend/src/pages/Nonprofits.tsx
deleted file mode 100644
index 818f81874d40e26d9d659b7258a50c94dd0890bd..0000000000000000000000000000000000000000
--- a/frontend/src/pages/Nonprofits.tsx
+++ /dev/null
@@ -1,315 +0,0 @@
-import { useState, useEffect } from 'react'
-import { formatCurrency } from '../utils/formatters'
-
-interface Nonprofit {
- source: string
- ein: string
- name: string
- city?: string
- state?: string
- description?: string
- mission?: string
- logo_url?: string
- website_url?: string
- ntee_code?: string
- ntee_description?: string
- revenue_amount?: number
- asset_amount?: number
- income_amount?: number
-}
-
-interface SearchParams {
- location: string
- keyword: string
- state: string
- nteeCode: string
-}
-
-export default function Nonprofits() {
- const [nonprofits, setNonprofits] = useState([])
- const [loading, setLoading] = useState(false)
- const [error, setError] = useState(null)
- const [searchParams, setSearchParams] = useState({
- location: 'Tuscaloosa, AL',
- keyword: 'dental',
- state: '',
- nteeCode: 'E' // E = Health
- })
-
- const searchNonprofits = async () => {
- setLoading(true)
- setError(null)
-
- try {
- const params = new URLSearchParams()
- if (searchParams.location) params.append('location', searchParams.location)
- if (searchParams.keyword) params.append('keyword', searchParams.keyword)
- if (searchParams.state) params.append('state', searchParams.state)
- if (searchParams.nteeCode) params.append('ntee_code', searchParams.nteeCode)
-
- const response = await fetch(`/api/nonprofits?${params}`)
- if (!response.ok) throw new Error('Failed to fetch nonprofits')
-
- const data = await response.json()
- setNonprofits(data.nonprofits || [])
- } catch (err) {
- setError(err instanceof Error ? err.message : 'An error occurred')
- } finally {
- setLoading(false)
- }
- }
-
- useEffect(() => {
- searchNonprofits()
- }, [])
-
- // formatCurrency imported from utils/formatters
-
- return (
-
-
-
-
Local Charities
-
- Find charities and nonprofits providing services in your community
-
-
-
- {/* Search Form */}
-
-
-
-
- Location
-
- setSearchParams({ ...searchParams, location: e.target.value })}
- className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
- placeholder="City, State"
- />
-
-
-
-
- Keyword
-
- setSearchParams({ ...searchParams, keyword: e.target.value })}
- className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
- placeholder="dental, health, etc."
- />
-
-
-
-
- NTEE Code
-
- setSearchParams({ ...searchParams, nteeCode: e.target.value })}
- className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
- >
- All Categories
- Health (E)
- Hospitals (E20)
- Community Clinics (E30)
- School-Based Health (E32)
- Education (B)
- Human Services (P)
-
-
-
-
-
- {loading ? 'Searching...' : 'Search'}
-
-
-
-
-
-
-
- {/* Error Message */}
- {error && (
-
- {error}
-
- )}
-
- {/* Results */}
-
-
-
- {loading ? 'Loading...' : `Found ${nonprofits.length} Organizations`}
-
-
-
-
- {nonprofits.length === 0 && !loading ? (
-
- No nonprofits found. Try adjusting your search criteria.
-
- ) : (
- nonprofits.map((org, index) => (
-
-
-
-
- {org.logo_url ? (
-
{
- e.currentTarget.style.display = 'none'
- const fallback = e.currentTarget.nextElementSibling as HTMLElement | null
- if (fallback) fallback.style.display = 'flex'
- }}
- />
- ) : null}
-
- {org.name.charAt(0)}
-
-
-
- {org.name}
-
-
- EIN: {org.ein} • {org.city}, {org.state}
-
-
-
-
- {(org.description || org.mission) && (
-
- {org.description || org.mission}
-
- )}
-
-
- {org.ntee_description && (
-
- {org.ntee_description}
-
- )}
-
- Source: {org.source}
-
-
-
- {/* Financial Info (from ProPublica) */}
- {(org.revenue_amount || org.asset_amount) && (
-
- {org.revenue_amount && (
-
- Revenue:
-
- {formatCurrency(org.revenue_amount)}
-
-
- )}
- {org.asset_amount && (
-
- Assets:
-
- {formatCurrency(org.asset_amount)}
-
-
- )}
- {org.income_amount && (
-
- Income:
-
- {formatCurrency(org.income_amount)}
-
-
- )}
-
- )}
-
-
- {org.website_url && (
-
- Visit Website
-
- )}
-
-
- ))
- )}
-
-
-
- {/* Info Box */}
-
-
- Why This Matters
-
-
- When officials reject policy proposals with technical objections ("We can't do dental
- screenings - legal liability"), you can instantly show citizens the nonprofits{' '}
- already doing it successfully . This bypasses technocratic vetoes,
- creates accountability pressure, and mobilizes citizens with direct volunteer/donation pathways.
-
-
-
✓ Access financial data from 3+ million organizations
-
✓ Find local service providers already solving the problem
-
✓ Show working alternatives to government inaction
-
✓ All data is 100% free from public APIs
-
-
-
-
- )
-}
diff --git a/frontend/src/pages/NonprofitsHF.tsx b/frontend/src/pages/NonprofitsHF.tsx
deleted file mode 100644
index 17d66293e97469a3733afb45fbf2c17c75d000e2..0000000000000000000000000000000000000000
--- a/frontend/src/pages/NonprofitsHF.tsx
+++ /dev/null
@@ -1,408 +0,0 @@
-import { useState, useEffect } from 'react'
-import { useQuery } from '@tanstack/react-query'
-import { useSearchParams } from 'react-router-dom'
-import { searchNonprofits } from '../utils/huggingface'
-import { formatCurrency } from '../utils/formatters'
-
-const DATASET_NAME = "CommunityOne/one-nonprofits-organizations"
-
-// NTEE Code categories
-const NTEE_CATEGORIES = [
- { code: '', label: 'All Categories' },
- { code: 'E', label: 'Health (E)' },
- { code: 'E20', label: 'Hospitals & Medical Centers (E20)' },
- { code: 'E30', label: 'Community Health Centers (E30)' },
- { code: 'E40', label: 'Reproductive Health (E40)' },
- { code: 'E50', label: 'Rehabilitative Care (E50)' },
- { code: 'E60', label: 'Health Support Services (E60)' },
- { code: 'E70', label: 'Public Health (E70)' },
- { code: 'E80', label: 'Health - General & Financing (E80)' },
- { code: 'E90', label: 'Nursing Services (E90)' },
- { code: 'P', label: 'Human Services (P)' },
- { code: 'X', label: 'Religion-Related (X)' },
- { code: 'X20', label: 'Christian (X20)' },
- { code: 'X21', label: 'Protestant (X21)' },
- { code: 'X22', label: 'Roman Catholic (X22)' },
- { code: 'X30', label: 'Jewish (X30)' },
- { code: 'X40', label: 'Islamic (X40)' },
- { code: 'B', label: 'Education (B)' },
- { code: 'C', label: 'Environment (C)' },
- { code: 'D', label: 'Animal-Related (D)' },
- { code: 'F', label: 'Mental Health (F)' },
- { code: 'G', label: 'Disease-Specific (G)' },
- { code: 'H', label: 'Medical Research (H)' },
-]
-
-// U.S. States
-const US_STATES = [
- { code: '', label: 'All States' },
- { code: 'AL', label: 'Alabama' },
- { code: 'AK', label: 'Alaska' },
- { code: 'AZ', label: 'Arizona' },
- { code: 'AR', label: 'Arkansas' },
- { code: 'CA', label: 'California' },
- { code: 'CO', label: 'Colorado' },
- { code: 'CT', label: 'Connecticut' },
- { code: 'DE', label: 'Delaware' },
- { code: 'FL', label: 'Florida' },
- { code: 'GA', label: 'Georgia' },
- { code: 'HI', label: 'Hawaii' },
- { code: 'ID', label: 'Idaho' },
- { code: 'IL', label: 'Illinois' },
- { code: 'IN', label: 'Indiana' },
- { code: 'IA', label: 'Iowa' },
- { code: 'KS', label: 'Kansas' },
- { code: 'KY', label: 'Kentucky' },
- { code: 'LA', label: 'Louisiana' },
- { code: 'ME', label: 'Maine' },
- { code: 'MD', label: 'Maryland' },
- { code: 'MA', label: 'Massachusetts' },
- { code: 'MI', label: 'Michigan' },
- { code: 'MN', label: 'Minnesota' },
- { code: 'MS', label: 'Mississippi' },
- { code: 'MO', label: 'Missouri' },
- { code: 'MT', label: 'Montana' },
- { code: 'NE', label: 'Nebraska' },
- { code: 'NV', label: 'Nevada' },
- { code: 'NH', label: 'New Hampshire' },
- { code: 'NJ', label: 'New Jersey' },
- { code: 'NM', label: 'New Mexico' },
- { code: 'NY', label: 'New York' },
- { code: 'NC', label: 'North Carolina' },
- { code: 'ND', label: 'North Dakota' },
- { code: 'OH', label: 'Ohio' },
- { code: 'OK', label: 'Oklahoma' },
- { code: 'OR', label: 'Oregon' },
- { code: 'PA', label: 'Pennsylvania' },
- { code: 'RI', label: 'Rhode Island' },
- { code: 'SC', label: 'South Carolina' },
- { code: 'SD', label: 'South Dakota' },
- { code: 'TN', label: 'Tennessee' },
- { code: 'TX', label: 'Texas' },
- { code: 'UT', label: 'Utah' },
- { code: 'VT', label: 'Vermont' },
- { code: 'VA', label: 'Virginia' },
- { code: 'WA', label: 'Washington' },
- { code: 'WV', label: 'West Virginia' },
- { code: 'WI', label: 'Wisconsin' },
- { code: 'WY', label: 'Wyoming' },
-]
-
-export default function Nonprofits() {
- const [searchParams] = useSearchParams()
-
- // Initialize from URL parameters
- const [searchQuery, setSearchQuery] = useState(searchParams.get('search') || 'dental')
- const [state, setState] = useState(searchParams.get('state') || 'AL')
- const [nteeCode, setNteeCode] = useState('')
- const [page, setPage] = useState(0)
- const pageSize = 100
-
- // Update search query from URL when it changes
- useEffect(() => {
- const searchParam = searchParams.get('search')
- const stateParam = searchParams.get('state')
- if (searchParam) setSearchQuery(searchParam)
- if (stateParam) setState(stateParam)
- }, [searchParams])
-
- // Query HuggingFace dataset
- const { data: nonprofits, isLoading, error } = useQuery({
- queryKey: ['nonprofits-hf', searchQuery, state, nteeCode, page],
- queryFn: async () => {
- return await searchNonprofits({
- dataset: DATASET_NAME,
- query: searchQuery || undefined,
- state: state || undefined,
- nteeCode: nteeCode || undefined,
- limit: pageSize
- })
- },
- staleTime: 5 * 60 * 1000, // Cache for 5 minutes
- })
-
- // formatCurrency imported from utils/formatters
-
- const formatSubsection = (code?: string) => {
- const subsections: Record = {
- '3': '501(c)(3) - Charitable',
- '4': '501(c)(4) - Social Welfare',
- '5': '501(c)(5) - Labor/Agricultural',
- '6': '501(c)(6) - Business League',
- '7': '501(c)(7) - Social/Recreational',
- '8': '501(c)(8) - Fraternal Beneficiary',
- '9': '501(c)(9) - Employees Association',
- '10': '501(c)(10) - Domestic Fraternal',
- '13': '501(c)(13) - Cemetery Company',
- '19': '501(c)(19) - Veterans Organization',
- }
- return subsections[code || ''] || `501(c)(${code})`
- }
-
- return (
-
-
- {/* Header */}
-
-
- Nonprofit Organizations
-
-
- Explore 1.9M+ U.S. nonprofits from IRS EO-BMF via HuggingFace Datasets
-
-
-
- 📊 Source: HuggingFace Dataset
-
-
- ✅ 1,952,238 organizations
-
-
- 🔄 Updated Monthly (IRS)
-
-
-
-
- {/* Search Form */}
-
-
- {/* Search Query */}
-
-
- Search
-
- setSearchQuery(e.target.value)}
- className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
- placeholder="e.g., dental, clinic, food bank"
- />
-
-
- {/* State Filter */}
-
-
- State
-
- setState(e.target.value)}
- className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
- >
- {US_STATES.map(s => (
- {s.label}
- ))}
-
-
-
- {/* NTEE Category */}
-
-
- Category (NTEE)
-
- setNteeCode(e.target.value)}
- className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
- >
- {NTEE_CATEGORIES.map(cat => (
- {cat.label}
- ))}
-
-
-
- {/* Search Button */}
-
- setPage(0)} // Reset to page 0 on new search
- className="w-full px-4 py-2 text-white rounded-md hover:opacity-90 transition-opacity"
- style={{ backgroundColor: '#52796F' }}
- >
- 🔍 Search
-
-
-
-
- {/* Active Filters */}
-
- {searchQuery && (
-
- Query: "{searchQuery}"
-
- )}
- {state && (
-
- State: {US_STATES.find(s => s.code === state)?.label}
-
- )}
- {nteeCode && (
-
- Category: {NTEE_CATEGORIES.find(c => c.code === nteeCode)?.label}
-
- )}
-
-
-
- {/* Loading State */}
- {isLoading && (
-
-
-
Loading nonprofits from HuggingFace...
-
- )}
-
- {/* Error State */}
- {error && (
-
-
Error Loading Data
-
{error.message}
-
- Make sure the dataset is uploaded to HuggingFace. Run: python scripts/upload_nonprofits_to_hf.py --all
-
-
- )}
-
- {/* Results */}
- {!isLoading && !error && nonprofits && (
- <>
-
-
- Found {nonprofits.length} nonprofits
- {nonprofits.length === pageSize && ' (showing first 100)'}
-
-
-
-
- {nonprofits.map((org) => (
-
-
- {/* Organization Name */}
-
- {org.name}
-
-
- {/* EIN */}
-
- EIN: {org.ein}
-
-
- {/* Location */}
-
- 📍 Location: {' '}
- {org.city && org.state ? (
- <>
- {org.city}, {org.state} {org.zip_code}
- >
- ) : (
- org.state || 'N/A'
- )}
-
-
- {/* NTEE Code */}
- {org.ntee_code && (
-
-
- {org.ntee_code}
-
-
- )}
-
- {/* Subsection */}
- {org.subsection_code && (
-
- Type: {formatSubsection(org.subsection_code)}
-
- )}
-
- {/* Financials */}
-
-
- Assets: {formatCurrency(org.asset_amount)}
-
-
- Income: {formatCurrency(org.income_amount)}
-
-
- Revenue: {formatCurrency(org.revenue_amount)}
-
-
-
- {/* Status */}
- {org.tax_exempt_status && (
-
-
- {org.tax_exempt_status === '1' ? '✅ Tax-Exempt' : 'Status: ' + org.tax_exempt_status}
-
-
- )}
-
- {/* Ruling Date */}
- {org.ruling_date && (
-
- Ruling Date: {org.ruling_date}
-
- )}
-
-
- ))}
-
-
- {/* No Results */}
- {nonprofits.length === 0 && (
-
-
No nonprofits found matching your criteria
-
Try adjusting your filters or search query
-
- )}
- >
- )}
-
- {/* Data Source Attribution */}
-
-
- 📚 Data Source
-
-
-
- Source: IRS Exempt Organizations Business Master File (EO-BMF)
-
-
- Dataset: {' '}
-
- {DATASET_NAME}
-
-
-
- Records: 1,952,238 organizations
-
-
- Updated: Monthly by IRS
-
-
- License: Public Domain (U.S. government data)
-
-
-
- View IRS EO-BMF Documentation →
-
-
-
-
-
-
- )
-}
diff --git a/frontend/src/pages/OpenSource.tsx b/frontend/src/pages/OpenSource.tsx
deleted file mode 100644
index 68605a084c4c8285ab7248b4123481c7a9dc1f8b..0000000000000000000000000000000000000000
--- a/frontend/src/pages/OpenSource.tsx
+++ /dev/null
@@ -1,254 +0,0 @@
-import {
- CodeBracketIcon,
- RocketLaunchIcon,
- UserGroupIcon,
- DocumentTextIcon,
- StarIcon,
- ArrowTopRightOnSquareIcon,
-} from '@heroicons/react/24/outline'
-
-export default function OpenSource() {
- const features = [
- {
- icon: CodeBracketIcon,
- title: 'Open Source Codebase',
- description: 'All code is publicly available on GitHub under an open source license. Fork, contribute, or learn from our implementation.',
- link: 'https://github.com/getcommunityone/open-navigator-for-engagement',
- },
- {
- icon: DocumentTextIcon,
- title: 'Comprehensive Documentation',
- description: 'Detailed documentation for developers, including API docs, data models, deployment guides, and contribution guidelines.',
- link: import.meta.env.PROD ? '/docs/intro' : 'http://localhost:3000/docs/intro',
- },
- {
- icon: UserGroupIcon,
- title: 'Community Contributions',
- description: 'Join our community of civic tech developers. Submit issues, create pull requests, or participate in discussions.',
- link: 'https://github.com/getcommunityone/open-navigator-for-engagement/issues',
- },
- {
- icon: RocketLaunchIcon,
- title: 'Hackathons & Events',
- description: 'Participate in quarterly hackathons focused on civic engagement, government transparency, and community empowerment.',
- link: '/hackathons',
- },
- ]
-
- const techStack = [
- { category: 'Frontend', tech: 'React, TypeScript, Vite, TailwindCSS' },
- { category: 'Backend', tech: 'FastAPI, Python, PostgreSQL' },
- { category: 'Data', tech: 'Pandas, Parquet, HuggingFace Datasets' },
- { category: 'Deployment', tech: 'Docker, HuggingFace Spaces, Databricks' },
- { category: 'Documentation', tech: 'Docusaurus, Markdown' },
- ]
-
- return (
-
-
- {/* Header */}
-
-
-
-
- Open Source Projects
-
-
-
- Build with us. Contribute to civic tech. Make government data accessible to everyone.
-
-
-
- {/* Main Project Card */}
-
-
-
-
- Open Navigator for Engagement
-
-
- A comprehensive platform for civic engagement, government transparency, and community empowerment.
-
-
-
-
- Star on GitHub
-
-
-
-
-
-
-
- Key Features
-
-
-
- ✓
- Track 925 cities, counties, and school districts
-
-
- ✓
- Monitor 43,726 nonprofits and community organizations
-
-
- ✓
- Analyze government meeting transcripts with AI
-
-
- ✓
- Open datasets on HuggingFace
-
-
-
-
-
-
- Languages & Tools
-
-
- {['TypeScript', 'Python', 'React', 'FastAPI', 'PostgreSQL', 'Docker'].map((tech) => (
-
- {tech}
-
- ))}
-
-
-
-
-
-
-
- {/* Features Grid */}
-
-
How to Get Involved
-
-
-
- {/* Tech Stack */}
-
-
Technology Stack
-
- {techStack.map((item) => (
-
-
- {item.category}
-
-
{item.tech}
-
- ))}
-
-
-
- {/* Contribution Guidelines */}
-
-
Contribution Guidelines
-
-
- We welcome contributions from developers of all skill levels! Here's how to get started:
-
-
-
- Fork the repository and clone it to your local machine
-
-
- Create a new branch for your feature or bug fix
-
-
- Make your changes following our coding standards
-
-
- Write tests for new functionality
-
-
- Submit a pull request with a clear description of your changes
-
-
-
- See our{' '}
-
- CONTRIBUTING.md
- {' '}
- for detailed guidelines.
-
-
-
-
-
- )
-}
diff --git a/frontend/src/pages/Opportunities.tsx b/frontend/src/pages/Opportunities.tsx
deleted file mode 100644
index 2fa47c93cea82d2361502f37df2d6c18f684da5e..0000000000000000000000000000000000000000
--- a/frontend/src/pages/Opportunities.tsx
+++ /dev/null
@@ -1,164 +0,0 @@
-import { useState } from 'react'
-import { useQuery } from '@tanstack/react-query'
-import axios from 'axios'
-
-interface Opportunity {
- id: string
- state: string
- municipality: string
- topic: string
- urgency: string
- confidence: number
- meeting_date: string
- next_meeting: string
- talking_points: string[]
- contact_info: {
- email?: string
- phone?: string
- }
-}
-
-export default function Opportunities() {
- const [urgencyFilter, setUrgencyFilter] = useState(null)
-
- const { data: opportunities, isLoading } = useQuery({
- queryKey: ['opportunities-list', urgencyFilter],
- queryFn: async () => {
- const params = new URLSearchParams()
- if (urgencyFilter) params.append('urgency', urgencyFilter)
-
- const response = await axios.get(`/api/opportunities?${params}`)
- return response.data.opportunities || []
- },
- })
-
- const handleGenerateEmail = async (oppId: string) => {
- try {
- const response = await axios.post(`/api/advocacy/email/${oppId}`)
- // Download the generated email
- const blob = new Blob([response.data.content], { type: 'text/plain' })
- const url = window.URL.createObjectURL(blob)
- const a = document.createElement('a')
- a.href = url
- a.download = `advocacy-email-${oppId}.txt`
- a.click()
- } catch (error) {
- console.error('Failed to generate email:', error)
- }
- }
-
- return (
-
- {/* Filters */}
-
-
- Filter by Urgency
-
-
- {['critical', 'high', 'medium', 'low'].map((level) => (
- setUrgencyFilter(urgencyFilter === level ? null : level)}
- className={`px-4 py-2 rounded-lg capitalize ${
- urgencyFilter === level
- ? 'bg-primary-600 text-white'
- : 'bg-gray-200 text-gray-700 hover:bg-gray-300'
- }`}
- >
- {level}
-
- ))}
-
-
-
- {/* Opportunities List */}
-
- {isLoading ? (
-
Loading causes...
- ) : (
- opportunities?.map((opp) => (
-
-
-
-
- {opp.municipality}, {opp.state}
-
-
- {opp.topic.replace(/_/g, ' ')} • Meeting: {new Date(opp.meeting_date).toLocaleDateString()}
-
-
-
-
- {opp.urgency}
-
-
-
- {/* Talking Points */}
- {opp.talking_points && opp.talking_points.length > 0 && (
-
-
Key Talking Points:
-
- {opp.talking_points.map((point, idx) => (
- {point}
- ))}
-
-
- )}
-
- {/* Actions */}
-
-
handleGenerateEmail(opp.id)}
- className="btn-primary"
- >
- Generate Email
-
-
- {opp.contact_info?.email && (
-
- Contact via Email
-
- )}
-
- {opp.next_meeting && (
-
- 📅 Add to Calendar
-
- )}
-
-
- {/* Confidence Score */}
-
-
-
Confidence Score:
-
-
-
{(opp.confidence * 100).toFixed(0)}%
-
-
-
-
- ))
- )}
-
-
- )
-}
diff --git a/frontend/src/pages/PeopleFinder.tsx b/frontend/src/pages/PeopleFinder.tsx
deleted file mode 100644
index 98d317a09b1772ee94975a1148452ffb3b474e9d..0000000000000000000000000000000000000000
--- a/frontend/src/pages/PeopleFinder.tsx
+++ /dev/null
@@ -1,315 +0,0 @@
-import { useState } from 'react'
-import { useQuery } from '@tanstack/react-query'
-import axios from 'axios'
-import { MagnifyingGlassIcon, UserGroupIcon, AcademicCapIcon, UsersIcon, CodeBracketIcon } from '@heroicons/react/24/outline'
-import { useLocation } from '../contexts/LocationContext'
-import FollowButton from '../components/FollowButton'
-
-type PersonRole = 'decision-makers' | 'support' | 'public' | 'open-source'
-
-interface Person {
- id: number
- name: string
- role: PersonRole
- specificRole: string
- organization: string
- location: string
- contact?: string
-}
-
-const roleCategories = {
- 'decision-makers': {
- title: 'Decision Makers',
- subtitle: 'People who vote on policy and budgets',
- icon: UserGroupIcon,
- color: '#354F52',
- roles: [
- 'Community Representatives',
- 'The Decision Makers',
- 'Board Members',
- 'Policy Voters',
- ]
- },
- 'support': {
- title: 'Support Staff',
- subtitle: 'People who help and advise',
- icon: AcademicCapIcon,
- color: '#64748B',
- roles: [
- 'Expert Advisors',
- 'Community Guides',
- 'Program Staff',
- ]
- },
- 'public': {
- title: 'Community Members',
- subtitle: 'Residents and advocates',
- icon: UsersIcon,
- color: '#8a9d9e',
- roles: [
- 'Neighbors & Residents',
- 'Parents & Families',
- 'Advocates',
- ]
- },
- 'open-source': {
- title: 'Open Source Contributors',
- subtitle: 'Civic tech maintainers and developers',
- icon: CodeBracketIcon,
- color: '#06B6D4',
- roles: [
- 'Project Maintainers',
- 'Core Contributors',
- 'Community Developers',
- ]
- }
-}
-
-export default function PeopleFinder() {
- const [searchQuery, setSearchQuery] = useState('')
- const [selectedRole, setSelectedRole] = useState('all')
- const { location } = useLocation()
-
- // Fetch contacts from API
- const { data: contactsData, isLoading } = useQuery({
- queryKey: ['people-finder', location?.state],
- queryFn: async () => {
- const params: any = {
- q: 'mayor', // Search for mayors and other officials (broad search)
- types: 'contacts',
- limit: 1000 // Get many results
- }
-
- if (location && location.state) {
- params.state = location.state
- }
-
- const response = await axios.get('/api/search/', { params })
- return response.data
- },
- staleTime: 60000, // Cache for 1 minute
- })
-
- // Convert API contacts to Person format
- const people: Person[] = (contactsData?.results?.contacts || []).map((contact: any, index: number) => ({
- id: index + 1,
- name: contact.metadata.name,
- role: 'decision-makers' as PersonRole,
- specificRole: contact.metadata.title || 'Official',
- organization: contact.metadata.jurisdiction || 'Local Government',
- location: `${contact.metadata.jurisdiction || ''}, ${contact.metadata.state || ''}`.trim().replace(/^,\s*/, ''),
- contact: undefined,
- }))
-
- const filteredPeople = people.filter(person => {
- const matchesSearch = searchQuery === '' ||
- person.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
- person.organization.toLowerCase().includes(searchQuery.toLowerCase()) ||
- person.location.toLowerCase().includes(searchQuery.toLowerCase()) ||
- person.specificRole.toLowerCase().includes(searchQuery.toLowerCase())
-
- const matchesRole = selectedRole === 'all' || person.role === selectedRole
-
- return matchesSearch && matchesRole
- })
-
- return (
-
-
-
- Find Leaders
-
-
- Discover elected officials, decision makers, and community leaders
- {location && location.state && (
- in {location.state}
- )}
-
-
-
- {/* Loading State */}
- {isLoading && (
-
- )}
-
- {/* Search Bar */}
- {!isLoading && (
- <>
-
-
- setSearchQuery(e.target.value)}
- className="w-full px-4 py-3 pl-12 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent text-gray-900"
- />
-
-
-
-
- {/* Role Filter Cards */}
-
-
setSelectedRole('all')}
- className={`p-4 rounded-lg border-2 transition-all ${
- selectedRole === 'all'
- ? 'border-[#354F52] bg-[#354F52] bg-opacity-10'
- : 'border-gray-200 hover:border-gray-300'
- }`}
- >
-
-
All People
-
{people.length} total
-
-
-
- {(Object.keys(roleCategories) as PersonRole[]).map((roleKey) => {
- const category = roleCategories[roleKey]
- const Icon = category.icon
- const count = people.filter(p => p.role === roleKey).length
-
- return (
-
setSelectedRole(roleKey)}
- className={`p-4 rounded-lg border-2 transition-all ${
- selectedRole === roleKey
- ? 'border-[#354F52] bg-[#354F52] bg-opacity-10'
- : 'border-gray-200 hover:border-gray-300'
- }`}
- >
-
-
-
- {category.title}
-
-
{count} people
-
-
- )
- })}
-
-
- {/* Role Categories Explanation */}
-
- {(Object.keys(roleCategories) as PersonRole[]).map((roleKey) => {
- const category = roleCategories[roleKey]
- const Icon = category.icon
-
- return (
-
-
-
-
- {category.title}
-
-
-
{category.subtitle}
-
- {category.roles.map((role) => (
-
- • {role}
-
- ))}
-
-
- )
- })}
-
-
- {/* Results */}
-
-
- {selectedRole === 'all' ? 'All People' : roleCategories[selectedRole].title}
- ({filteredPeople.length} results)
-
-
- {filteredPeople.length === 0 ? (
-
-
No people found matching your criteria
-
- ) : (
-
- {filteredPeople.map((person) => {
- const category = roleCategories[person.role]
- const Icon = category.icon
-
- return (
-
-
-
-
-
- {person.name}
-
-
- {person.specificRole}
-
-
-
-
-
-
-
- {person.role === 'open-source' ? 'Repository:' : 'Organization:'}
-
- {person.role === 'open-source' ? (
-
- {person.organization}
-
- ) : (
-
{person.organization}
- )}
-
-
-
Location:
-
{person.location}
-
- {person.contact && (
-
-
Contact:
- {person.role === 'open-source' && person.contact.startsWith('@') ? (
-
- {person.contact}
-
- ) : (
-
{person.contact}
- )}
-
- )}
-
-
- {/* Follow button (LinkedIn/Facebook style) */}
-
-
-
-
- )
- })}
-
- )}
-
- >
- )}
-
- )
-}
diff --git a/frontend/src/pages/Profile.tsx b/frontend/src/pages/Profile.tsx
deleted file mode 100644
index e71887672bf5404b93fd03a45631aa3397a2757f..0000000000000000000000000000000000000000
--- a/frontend/src/pages/Profile.tsx
+++ /dev/null
@@ -1,409 +0,0 @@
-import { useState } from 'react'
-import { useQuery } from '@tanstack/react-query'
-import axios from 'axios'
-import { Tab } from '@headlessui/react'
-import {
- UserIcon,
- BuildingLibraryIcon,
- HeartIcon,
- Cog6ToothIcon
-} from '@heroicons/react/24/outline'
-import { useAuth } from '../contexts/AuthContext'
-import SocialStats from '../components/SocialStats'
-import FollowButton from '../components/FollowButton'
-
-interface Leader {
- id: number
- name: string
- slug: string
- title?: string
- photo_url?: string
- office?: string
- city?: string
- state?: string
- follower_count: number
- is_verified: boolean
-}
-
-interface Organization {
- id: number
- name: string
- slug: string
- description?: string
- logo_url?: string
- org_type?: string
- city?: string
- state?: string
- follower_count: number
- is_verified: boolean
-}
-
-interface Cause {
- id: number
- name: string
- slug: string
- description?: string
- icon_url?: string
- color?: string
- category?: string
- follower_count: number
-}
-
-function classNames(...classes: string[]) {
- return classes.filter(Boolean).join(' ')
-}
-
-export default function Profile() {
- const { user } = useAuth()
- const [selectedTab, setSelectedTab] = useState(0)
-
- const { data: leadersFollowing } = useQuery({
- queryKey: ['following', 'leaders'],
- queryFn: async () => {
- const response = await axios.get('/api/social/following/leaders')
- return response.data
- },
- enabled: !!user
- })
-
- const { data: orgsFollowing } = useQuery({
- queryKey: ['following', 'organizations'],
- queryFn: async () => {
- const response = await axios.get('/api/social/following/organizations')
- return response.data
- },
- enabled: !!user
- })
-
- const { data: causesFollowing } = useQuery({
- queryKey: ['following', 'causes'],
- queryFn: async () => {
- const response = await axios.get('/api/social/following/causes')
- return response.data
- },
- enabled: !!user
- })
-
- if (!user) {
- return (
-
-
-
- Please sign in
-
-
You need to be signed in to view your profile
-
-
- )
- }
-
- return (
-
-
- {/* Profile Header */}
-
-
- {/* Avatar */}
-
- {user.avatar_url ? (
-
- ) : (
-
- {(user.full_name || user.email).charAt(0).toUpperCase()}
-
- )}
-
-
- {/* Profile Info */}
-
-
- {user.full_name || 'Community Member'}
-
-
- {user.email}
-
-
- {user.city && user.state && (
-
- 📍 {user.city}, {user.state}
-
- )}
-
- {/* Social Stats */}
-
-
-
- {/* Edit Profile Button */}
-
-
- Edit Profile
-
-
-
-
- {/* Following Tabs */}
-
-
- Following
-
-
-
-
-
- classNames(
- 'px-4 py-2 font-medium text-sm border-b-2 transition-colors',
- selected
- ? 'border-[#354F52] text-[#354F52]'
- : 'border-transparent text-gray-500 hover:text-gray-700'
- )
- }
- >
-
-
- Leaders ({leadersFollowing?.length || 0})
-
-
-
- classNames(
- 'px-4 py-2 font-medium text-sm border-b-2 transition-colors',
- selected
- ? 'border-[#354F52] text-[#354F52]'
- : 'border-transparent text-gray-500 hover:text-gray-700'
- )
- }
- >
-
-
- Charities ({orgsFollowing?.length || 0})
-
-
-
- classNames(
- 'px-4 py-2 font-medium text-sm border-b-2 transition-colors',
- selected
- ? 'border-[#354F52] text-[#354F52]'
- : 'border-transparent text-gray-500 hover:text-gray-700'
- )
- }
- >
-
-
- Causes ({causesFollowing?.length || 0})
-
-
-
-
-
- {/* Leaders */}
-
- {!leadersFollowing || leadersFollowing.length === 0 ? (
-
- ) : (
-
- {leadersFollowing.map((leader) => (
-
- {leader.photo_url ? (
-
- ) : (
-
- {leader.name.charAt(0)}
-
- )}
-
-
- {leader.name}
- {leader.is_verified && ✓ }
-
- {leader.title &&
{leader.title}
}
- {leader.office &&
{leader.office}
}
- {leader.city && leader.state && (
-
- {leader.city}, {leader.state}
-
- )}
-
- {leader.follower_count.toLocaleString()} followers
-
-
-
-
- ))}
-
- )}
-
-
- {/* Organizations */}
-
- {!orgsFollowing || orgsFollowing.length === 0 ? (
-
- ) : (
-
- {orgsFollowing.map((org) => (
-
- {org.logo_url ? (
-
- ) : (
-
- {org.name.charAt(0)}
-
- )}
-
-
- {org.name}
- {org.is_verified && ✓ }
-
- {org.org_type && (
-
- {org.org_type}
-
- )}
- {org.description && (
-
{org.description}
- )}
- {org.city && org.state && (
-
- {org.city}, {org.state}
-
- )}
-
- {org.follower_count.toLocaleString()} followers
-
-
-
-
- ))}
-
- )}
-
-
- {/* Causes */}
-
- {!causesFollowing || causesFollowing.length === 0 ? (
-
- ) : (
-
- {causesFollowing.map((cause) => (
-
-
- {cause.icon_url ? (
-
- ) : (
-
- )}
-
- {cause.name}
-
-
- {cause.description && (
-
{cause.description}
- )}
- {cause.category && (
-
- {cause.category}
-
- )}
-
-
- {cause.follower_count.toLocaleString()} followers
-
-
-
-
- ))}
-
- )}
-
-
-
-
-
-
- )
-}
diff --git a/frontend/src/pages/Services.tsx b/frontend/src/pages/Services.tsx
deleted file mode 100644
index e1ef1ca2fe18ac4df05d0c9c83669a9003d0bd96..0000000000000000000000000000000000000000
--- a/frontend/src/pages/Services.tsx
+++ /dev/null
@@ -1,155 +0,0 @@
-import { useEffect } from 'react';
-import { HeartIcon, HomeIcon, AcademicCapIcon, PhoneIcon, MapIcon } from '@heroicons/react/24/outline';
-
-export default function Services() {
- // Scroll to top with smooth behavior when page loads
- useEffect(() => {
- window.scrollTo({
- top: 0,
- behavior: 'smooth'
- });
- }, []);
-
- return (
-
- {/* Page Header */}
-
-
-
-
-
-
-
Services & Resources
-
Find family services, social programs, and community support resources
-
-
-
-
- {/* Coming Soon Notice */}
-
-
-
-
-
-
-
Coming Soon!
-
- We're creating a comprehensive directory of family services and community resources.
-
-
-
What you'll find here:
-
- Healthcare services including dental clinics and mental health support
- Educational programs, tutoring, and after-school activities
- Food assistance, housing support, and financial aid programs
- Legal aid, translation services, and crisis hotlines
- Parks & recreation programs, dog parks, and community facilities
- Senior services, youth activities, and family programs
-
-
-
-
-
-
- {/* Preview Service Categories */}
-
- {/* Health Services */}
-
-
-
-
-
-
Health Services
-
-
- • Free dental clinics
- • Community health centers
- • Mental health counseling
- • Vision screening programs
-
-
-
- {/* Education */}
-
-
-
- • After-school programs
- • Tutoring services
- • Adult education
- • STEM workshops
-
-
-
- {/* Housing & Basic Needs */}
-
-
-
- • Food pantries
- • Housing assistance
- • Utility support
- • Emergency shelters
-
-
-
- {/* Parks & Recreation */}
-
-
-
-
-
-
Parks & Recreation
-
-
- • Community parks
- • Dog parks & pet areas
- • Sports facilities
- • Recreation programs
-
-
-
-
- {/* Emergency Contacts Card */}
-
-
-
-
Emergency Resources
-
-
-
-
Crisis Hotline
-
988 - Suicide & Crisis Lifeline
-
-
-
Domestic Violence
-
1-800-799-7233 (SAFE)
-
-
-
Child Abuse
-
1-800-422-4453
-
-
-
-
- {/* Temporary Link */}
-
-
- );
-}
diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx
deleted file mode 100644
index 49b293abea1c7b075750f87938a528fe02274aa8..0000000000000000000000000000000000000000
--- a/frontend/src/pages/Settings.tsx
+++ /dev/null
@@ -1,316 +0,0 @@
-import { useState, useEffect } from 'react'
-import { useAuth } from '../contexts/AuthContext'
-import AddressLookup from '../components/AddressLookup'
-import {
- MapPinIcon,
- UserCircleIcon,
- CheckCircleIcon,
- ExclamationCircleIcon
-} from '@heroicons/react/24/outline'
-
-export default function Settings() {
- const { user } = useAuth()
- const [useAddressLookup, setUseAddressLookup] = useState(false)
- const [locationData, setLocationData] = useState({
- state: user?.state || '',
- county: user?.county || '',
- city: user?.city || '',
- school_board: user?.school_board || '',
- })
- const [isSaving, setIsSaving] = useState(false)
- const [saveStatus, setSaveStatus] = useState<'success' | 'error' | null>(null)
- const [errors, setErrors] = useState>({})
-
- const API_URL = import.meta.env.PROD ? '/api' : 'http://localhost:8000'
-
- // Update form when user data changes
- useEffect(() => {
- if (user) {
- setLocationData({
- state: user.state || '',
- county: user.county || '',
- city: user.city || '',
- school_board: user.school_board || '',
- })
- }
- }, [user])
-
- const handleAddressFound = (location: any) => {
- setLocationData({
- state: location.state,
- county: location.county,
- city: location.city,
- school_board: locationData.school_board, // Keep existing school board
- })
- setUseAddressLookup(false) // Switch back to manual mode after lookup
- }
-
- const handleChange = (field: string, value: string) => {
- setLocationData(prev => ({ ...prev, [field]: value }))
- // Clear error for this field
- if (errors[field]) {
- setErrors(prev => {
- const newErrors = { ...prev }
- delete newErrors[field]
- return newErrors
- })
- }
- // Clear save status
- setSaveStatus(null)
- }
-
- const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault()
-
- // Validate required fields
- const newErrors: Record = {}
- if (!locationData.state.trim()) newErrors.state = 'State is required'
- if (!locationData.county.trim()) newErrors.county = 'County is required'
- if (!locationData.city.trim()) newErrors.city = 'City is required'
-
- if (Object.keys(newErrors).length > 0) {
- setErrors(newErrors)
- return
- }
-
- setIsSaving(true)
- setSaveStatus(null)
-
- try {
- const token = localStorage.getItem('auth_token')
- const response = await fetch(`${API_URL}/auth/profile`, {
- method: 'PATCH',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${token}`,
- },
- body: JSON.stringify(locationData),
- })
-
- if (response.ok) {
- setSaveStatus('success')
- // Trigger a page reload to refresh user data (or update context)
- setTimeout(() => window.location.reload(), 1500)
- } else {
- setSaveStatus('error')
- }
- } catch (error) {
- console.error('Failed to update location:', error)
- setSaveStatus('error')
- } finally {
- setIsSaving(false)
- }
- }
-
- if (!user) {
- return (
-
-
-
Please sign in to access settings.
-
-
- )
- }
-
- return (
-
-
- Settings
-
-
- {/* Profile Section */}
-
-
-
-
-
Profile Information
-
-
-
-
-
- {/* Location Preferences */}
-
-
-
-
-
Location Preferences
-
-
- Help us show you relevant local government and nonprofit information
-
-
-
-
-
- )
-}
diff --git a/frontend/src/pages/UnifiedSearch.tsx b/frontend/src/pages/UnifiedSearch.tsx
deleted file mode 100644
index c0567e899dfb453bd432822fdf594e4897e03ad5..0000000000000000000000000000000000000000
--- a/frontend/src/pages/UnifiedSearch.tsx
+++ /dev/null
@@ -1,1480 +0,0 @@
-import { useState, useRef, useEffect, Fragment } from 'react'
-import { useNavigate, useSearchParams, Link } from 'react-router-dom'
-import { useQuery } from '@tanstack/react-query'
-import axios from 'axios'
-import { Menu, Transition } from '@headlessui/react'
-import {
- MagnifyingGlassIcon,
- UserIcon,
- CalendarIcon,
- BuildingOfficeIcon,
- HeartIcon,
- XMarkIcon,
- AdjustmentsHorizontalIcon,
- CheckIcon,
- MapPinIcon,
- ChevronDownIcon,
- ChevronUpIcon,
- GlobeAltIcon,
- VideoCameraIcon,
- Cog6ToothIcon,
- ArrowRightOnRectangleIcon
-} from '@heroicons/react/24/outline'
-import { useAuth } from '../contexts/AuthContext'
-import { formatCurrency } from '../utils/formatters'
-
-interface SearchResult {
- type: 'contact' | 'meeting' | 'organization' | 'cause'
- title: string
- subtitle: string
- description: string
- url: string
- score: number
- metadata: Record
-}
-
-interface SearchResponse {
- query: string
- total_results: number
- results: {
- contacts: SearchResult[]
- meetings: SearchResult[]
- organizations: SearchResult[]
- causes: SearchResult[]
- jurisdictions?: SearchResult[]
- }
- pagination: {
- page: number
- limit: number
- offset: number
- total_pages: number
- has_next: boolean
- has_prev: boolean
- }
- filters: {
- state?: string
- ntee_code?: string
- types: string[]
- }
-}
-
-export default function UnifiedSearch() {
- const navigate = useNavigate()
- const [searchParams, setSearchParams] = useSearchParams()
-
- // Initialize state directly from URL params (lazy initializer for performance)
- const [query, setQuery] = useState(() => searchParams.get('q') || '')
- const [activeQuery, setActiveQuery] = useState(() => searchParams.get('q') || '')
- const [selectedTypes, setSelectedTypes] = useState(() => {
- const typesParam = searchParams.get('types')
- if (typesParam) {
- const types = typesParam.split(',').filter(t =>
- ['contacts', 'organizations', 'causes', 'meetings'].includes(t.trim())
- )
- return types.length > 0 ? types : ['contacts', 'organizations', 'causes']
- }
- return ['contacts', 'organizations', 'causes']
- })
- const [selectedState, setSelectedState] = useState(() => searchParams.get('state') || '')
- const [currentPage, setCurrentPage] = useState(() => parseInt(searchParams.get('page') || '1'))
- const [showFilters, setShowFilters] = useState(false)
- const [showSuggestions, setShowSuggestions] = useState(false)
- const [sortBy, setSortBy] = useState(() => searchParams.get('sort') || 'relevance')
- const [nteeCategory, setNteeCategory] = useState(() => searchParams.get('ntee') || '')
- const [jurisdictionDetails, setJurisdictionDetails] = useState(() => {
- const detailsParam = searchParams.get('jurisdiction_details')
- if (detailsParam) {
- try {
- return JSON.parse(decodeURIComponent(detailsParam))
- } catch (e) {
- return []
- }
- }
- return []
- })
-
- // Derived state: Always have state code available from URL OR jurisdiction details
- const [effectiveState, setEffectiveState] = useState(() => {
- const urlState = searchParams.get('state')
- if (urlState) return urlState
-
- // Extract from jurisdiction details if available
- const detailsParam = searchParams.get('jurisdiction_details')
- if (detailsParam) {
- try {
- const details = JSON.parse(decodeURIComponent(detailsParam))
- // Find state in jurisdiction hierarchy
- for (const j of details) {
- if (j.state) return j.state
- if (j.type === 'State' || j.type === 'state') {
- const stateMap: Record = {
- 'Massachusetts': 'MA', 'Alabama': 'AL', 'Georgia': 'GA',
- 'Washington': 'WA', 'Wisconsin': 'WI', 'California': 'CA',
- 'Texas': 'TX', 'New York': 'NY', 'Florida': 'FL'
- }
- return stateMap[j.name] || j.name
- }
- }
- } catch (e) {
- // Ignore parse errors
- }
- }
- return ''
- })
- const [expandedJurisdictions, setExpandedJurisdictions] = useState>(new Set())
- const [expandedOrganizations, setExpandedOrganizations] = useState>(new Set())
-
- const searchInputRef = useRef(null)
- const { user, isAuthenticated, login, logout, isLoading } = useAuth()
-
- // Initialize from URL parameters on mount
- useEffect(() => {
- const queryParam = searchParams.get('q')
- const stateParam = searchParams.get('state')
- const typesParam = searchParams.get('types')
- searchParams.get('page') // Read but don't store
- searchParams.get('sort') // Read but don't store
- searchParams.get('ntee') // Read but don't store
- const jurisdictionDetailsParam = searchParams.get('jurisdiction_details')
-
- if (queryParam) {
- setQuery(queryParam)
- setActiveQuery(queryParam)
- }
- if (stateParam) {
- setSelectedState(stateParam)
- setEffectiveState(stateParam)
- } else if (jurisdictionDetailsParam) {
- // Extract state from jurisdiction details
- try {
- const details = JSON.parse(decodeURIComponent(jurisdictionDetailsParam))
- setJurisdictionDetails(details)
-
- // Find and set effective state
- for (const j of details) {
- if (j.state) {
- setEffectiveState(j.state)
- break
- }
- if (j.type === 'State' || j.type === 'state') {
- const stateMap: Record = {
- 'Massachusetts': 'MA', 'Alabama': 'AL', 'Georgia': 'GA',
- 'Washington': 'WA', 'Wisconsin': 'WI', 'California': 'CA',
- 'Texas': 'TX', 'New York': 'NY', 'Florida': 'FL'
- }
- const stateCode = stateMap[j.name] || j.name
- setEffectiveState(stateCode)
- break
- }
- }
- } catch (e) {
- setJurisdictionDetails([])
- }
- }
- if (typesParam) {
- const types = typesParam.split(',').filter(t =>
- ['contacts', 'meetings', 'organizations', 'causes'].includes(t.trim())
- )
- if (types.length > 0) {
- setSelectedTypes(types)
- }
- }
- }, [searchParams, effectiveState])
-
- // Preview/autocomplete query for search suggestions
- const { data: previewResults } = useQuery({
- queryKey: ['search-preview', query, effectiveState],
- queryFn: async () => {
- if (!query || query.length < 2) return null
-
- const params: any = {
- q: query,
- types: 'causes,contacts,organizations',
- limit: 3
- }
-
- // Use effectiveState - already computed from URL or jurisdiction details
- if (effectiveState) {
- params.state = effectiveState
- }
-
- const response = await axios.get('/api/search/', { params })
- return response.data
- },
- enabled: query.length >= 2 && showSuggestions,
- staleTime: 1000 // Cache for 1 second to avoid excessive requests
- })
-
- // Main search results
- const { data: searchResults, isLoading: isSearching, error } = useQuery({
- queryKey: ['unified-search', activeQuery, selectedTypes, selectedState, currentPage, sortBy, nteeCategory],
- queryFn: async () => {
- // Allow searching with query OR with filters (browse mode)
- if (!activeQuery && !selectedState && !selectedTypes.length) {
- return null
- }
-
- const params: any = {
- types: selectedTypes.join(','),
- limit: 20,
- page: currentPage
- }
-
- // Query is optional - can browse by state/type
- if (activeQuery) {
- params.q = activeQuery
- }
-
- if (selectedState) {
- params.state = selectedState
- }
-
- // Add sort and filter parameters
- if (sortBy && sortBy !== 'relevance') {
- params.sort = sortBy
- }
-
- if (nteeCategory) {
- params.ntee_code = nteeCategory
- }
-
- const response = await axios.get('/api/search/', { params })
- return response.data
- },
- // Enable if we have query OR filters (browse mode)
- enabled: (activeQuery && activeQuery.length >= 2) || selectedState !== '' || selectedTypes.length > 0
- })
-
- const handleSearch = (e?: React.FormEvent) => {
- e?.preventDefault()
- // Allow search with query OR just filters (browse mode)
- if (query.trim().length >= 2 || selectedState || selectedTypes.length > 0) {
- setActiveQuery(query)
- setShowSuggestions(false)
- setCurrentPage(1) // Reset to first page on new search
-
- // Update URL
- const params: any = {}
- if (query.trim()) params.q = query
- if (selectedState) params.state = selectedState
- if (selectedTypes.length > 0 && selectedTypes.length < 5) {
- params.types = selectedTypes.join(',')
- }
- if (sortBy && sortBy !== 'relevance') params.sort = sortBy
- if (nteeCategory) params.ntee = nteeCategory
- setSearchParams(params)
- }
- }
-
- const handlePageChange = (newPage: number) => {
- setCurrentPage(newPage)
-
- // Update URL
- const params: any = {}
- if (activeQuery) params.q = activeQuery
- if (selectedState) params.state = selectedState
- if (selectedTypes.length > 0 && selectedTypes.length < 5) {
- params.types = selectedTypes.join(',')
- }
- if (sortBy && sortBy !== 'relevance') params.sort = sortBy
- if (nteeCategory) params.ntee = nteeCategory
- if (newPage > 1) params.page = newPage.toString()
- setSearchParams(params)
-
- // Scroll to top
- window.scrollTo({ top: 0, behavior: 'smooth' })
- }
-
- const handleViewAllCategory = (category: string) => {
- setActiveQuery(query)
- setShowSuggestions(false)
- setSelectedTypes([category])
-
- // Update URL with all current filters
- const params: any = { q: query }
- if (selectedState) params.state = selectedState
- params.types = category
- if (sortBy && sortBy !== 'relevance') params.sort = sortBy
- if (nteeCategory) params.ntee = nteeCategory
- setSearchParams(params)
- }
-
- const toggleType = (type: string) => {
- const newTypes = selectedTypes.includes(type)
- ? selectedTypes.filter(t => t !== type)
- : [...selectedTypes, type]
-
- setSelectedTypes(newTypes)
- setCurrentPage(1)
-
- // Update URL with all current filters
- const params: any = {}
- if (activeQuery) params.q = activeQuery
- if (selectedState) params.state = selectedState
- if (newTypes.length > 0 && newTypes.length < 5) {
- params.types = newTypes.join(',')
- }
- if (sortBy && sortBy !== 'relevance') params.sort = sortBy
- if (nteeCategory) params.ntee = nteeCategory
- setSearchParams(params)
- }
-
- const toggleJurisdictionExpansion = (index: number) => {
- setExpandedJurisdictions(prev => {
- const newSet = new Set(prev)
- if (newSet.has(index)) {
- newSet.delete(index)
- } else {
- newSet.add(index)
- }
- return newSet
- })
- }
-
- const toggleOrganizationExpansion = (ein: string) => {
- setExpandedOrganizations(prev => {
- const newSet = new Set(prev)
- if (newSet.has(ein)) {
- newSet.delete(ein)
- } else {
- newSet.add(ein)
- }
- return newSet
- })
- }
-
- const getTypeIcon = (type: string) => {
- switch (type) {
- case 'contact':
- return
- case 'meeting':
- return
- case 'organization':
- return
- case 'cause':
- return
- default:
- return null
- }
- }
-
- const getTypeColor = (type: string) => {
- switch (type) {
- case 'contact':
- return 'bg-blue-100 text-blue-700 border-blue-200'
- case 'meeting':
- return 'bg-green-100 text-green-700 border-green-200'
- case 'organization':
- return 'bg-purple-100 text-purple-700 border-purple-200'
- case 'cause':
- return 'bg-pink-100 text-pink-700 border-pink-200'
- default:
- return 'bg-gray-100 text-gray-700 border-gray-200'
- }
- }
-
- const ResultCard = ({ result }: { result: SearchResult }) => (
-
-
-
- {getTypeIcon(result.type)}
-
-
-
-
-
-
navigate(result.url)}
- className="font-semibold text-gray-900 cursor-pointer hover:text-blue-600 mb-1"
- >
- {result.title}
-
-
{result.subtitle}
-
-
- {/* Logo for organizations */}
- {result.type === 'organization' && (
- result.metadata?.logo_url ? (
-
{
- e.currentTarget.style.display = 'none'
- const fallback = e.currentTarget.nextElementSibling as HTMLElement | null
- if (fallback) fallback.style.display = 'flex'
- }}
- />
- ) : null
- )}
- {result.type === 'organization' && (
-
- {result.title.charAt(0)}
-
- )}
-
-
-
{result.description}
-
- {/* Mission statement for organizations */}
- {result.type === 'organization' && result.metadata?.mission && (
-
-
- Mission:
- {result.metadata.mission}
-
-
- )}
-
- {/* Website link for organizations */}
- {result.type === 'organization' && result.metadata?.website && (
-
e.stopPropagation()}
- className="text-sm text-blue-600 hover:text-blue-800 hover:underline inline-flex items-center gap-1 mb-2"
- >
- 🔗 {result.metadata.website}
-
- )}
-
- {/* Additional metadata for organizations */}
- {result.type === 'organization' && result.metadata && (
-
- {result.metadata.ein && (
-
- EIN: {result.metadata.ein}
-
- )}
- {result.metadata.revenue && result.metadata.revenue > 0 && (
-
- 💰 Revenue: {formatCurrency(result.metadata.revenue)}
- {result.metadata.tax_year && ` (${result.metadata.tax_year})`}
-
- )}
- {result.metadata.assets && result.metadata.assets > 0 && (
-
- 📊 Assets: {formatCurrency(result.metadata.assets)}
- {result.metadata.tax_year && ` (${result.metadata.tax_year})`}
-
- )}
- {result.metadata.causes && result.metadata.causes.length > 0 && (
-
- 🏷️ {result.metadata.causes.slice(0, 3).join(', ')}
-
- )}
-
- )}
-
- {/* Expandable details for organizations */}
- {result.type === 'organization' && result.metadata?.ein && (
-
-
{
- e.stopPropagation()
- toggleOrganizationExpansion(result.metadata.ein)
- }}
- className="flex items-center gap-2 text-sm text-gray-600 hover:text-gray-900 font-medium"
- >
- {expandedOrganizations.has(result.metadata.ein) ? (
-
- ) : (
-
- )}
- {expandedOrganizations.has(result.metadata.ein) ? 'Hide' : 'Show'} Details
-
-
- {expandedOrganizations.has(result.metadata.ein) && (
-
- {/* Financials Section */}
-
-
- 💰 Financial Information
- {result.metadata.tax_year && (
- (Tax Year {result.metadata.tax_year})
- )}
-
-
- {/* Check if ANY financial data exists */}
- {!result.metadata.revenue && !result.metadata.assets && !result.metadata.income ? (
-
-
- 📊 Form 990 data not yet available
-
-
- Financial information from IRS Form 990 filings is being enriched.
- Check back later or visit{' '}
-
- ProPublica Nonprofit Explorer
-
- {' '}for current data.
-
-
- ) : (
-
-
-
Total Revenue
-
- {result.metadata.revenue !== null && result.metadata.revenue !== undefined
- ? formatCurrency(result.metadata.revenue)
- : Pending }
-
-
-
-
Total Assets
-
- {result.metadata.assets !== null && result.metadata.assets !== undefined
- ? formatCurrency(result.metadata.assets)
- : Pending }
-
-
-
-
Net Income
-
- {result.metadata.income !== null && result.metadata.income !== undefined
- ? formatCurrency(result.metadata.income)
- : Pending }
-
-
-
- )}
-
-
- {/* Board Members Section - Placeholder */}
-
-
- 👥 Board Members
-
-
- Board member information coming soon
-
-
-
- {/* Grants Section - Placeholder */}
-
-
- 📜 Recent Grants
-
-
- Grant information coming soon
-
-
-
- )}
-
- )}
-
- {/* Type badge */}
-
-
- {getTypeIcon(result.type)}
- {result.type.charAt(0).toUpperCase() + result.type.slice(1)}
-
-
-
-
-
- )
-
- return (
-
- {/* Top Navigation Bar */}
-
-
-
-
-
- Open Navigator
-
-
-
- {/* Login/Avatar */}
-
- {isLoading ? (
-
- ) : isAuthenticated && user ? (
-
-
- {user.avatar_url ? (
- {
- e.currentTarget.style.display = 'none';
- const fallback = e.currentTarget.nextElementSibling as HTMLElement | null;
- if (fallback) fallback.style.display = 'flex';
- }}
- />
- ) : null}
-
- {(user.full_name || user.username || user.email).charAt(0).toUpperCase()}
-
-
- {user.full_name || user.username || user.email.split('@')[0]}
-
-
-
-
-
-
-
-
{user.full_name || user.username}
-
{user.email}
-
-
-
- {({ active }) => (
-
-
- Settings
-
- )}
-
-
- {({ active }) => (
-
-
- Sign Out
-
- )}
-
-
-
-
-
- ) : (
-
login('huggingface')}
- className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors font-medium"
- >
- Login
-
- )}
-
-
-
-
-
- {/* Search Header */}
-
-
Search
-
- {/* Search Bar */}
-
-
- {/* Filter Bar */}
-
-
setShowFilters(!showFilters)}
- className={`flex items-center gap-2 px-4 py-2 rounded-lg border-2 transition-colors ${
- showFilters
- ? 'border-primary-500 bg-primary-50 text-primary-700'
- : 'border-gray-300 text-gray-700 hover:border-gray-400 hover:bg-gray-50'
- }`}
- >
-
- Filters
- {(selectedState || sortBy !== 'relevance' || nteeCategory) && (
-
- {[selectedState, sortBy !== 'relevance' ? 'sorted' : null, nteeCategory].filter(Boolean).length}
-
- )}
-
-
- {/* Quick Type Filters - Meetings pill on far right */}
- {(['contacts', 'organizations', 'causes', 'meetings'] as const).map((type) => (
-
toggleType(type)}
- className={`flex items-center gap-2 px-4 py-2 rounded-full border-2 transition-all ${
- selectedTypes.includes(type)
- ? `${getTypeColor(type)} border-current font-medium shadow-sm`
- : 'border-gray-300 bg-white text-gray-600 hover:border-gray-400 hover:bg-gray-50'
- }`}
- >
- {selectedTypes.includes(type) && (
-
- )}
- {getTypeIcon(type)}
- {type.charAt(0).toUpperCase() + type.slice(1)}
-
- ))}
-
-
- {/* Active Filters Display */}
- {(selectedState || sortBy !== 'relevance' || nteeCategory || jurisdictionDetails.length > 0) && (
-
- Active filters:
- {selectedState && (
-
- State: {selectedState}
- {
- setSelectedState('')
- setTimeout(() => handleSearch(), 0)
- }}
- className="hover:bg-blue-200 rounded-full p-0.5"
- >
-
-
-
- )}
- {jurisdictionDetails.length > 0 && (
-
-
- {jurisdictionDetails.length} Jurisdictions
- {
- setJurisdictionDetails([])
- const params = new URLSearchParams(window.location.search)
- params.delete('jurisdiction_details')
- setSearchParams(params)
- }}
- className="hover:bg-teal-200 rounded-full p-0.5"
- >
-
-
-
- )}
- {sortBy !== 'relevance' && (
-
- Sort: {
- sortBy === 'name-asc' ? 'Name A-Z' :
- sortBy === 'name-desc' ? 'Name Z-A' :
- sortBy === 'revenue-desc' ? 'Revenue ↓' :
- sortBy === 'revenue-asc' ? 'Revenue ↑' :
- sortBy === 'assets-desc' ? 'Assets ↓' :
- sortBy === 'assets-asc' ? 'Assets ↑' : sortBy
- }
- {
- setSortBy('relevance')
- setTimeout(() => handleSearch(), 0)
- }}
- className="hover:bg-purple-200 rounded-full p-0.5"
- >
-
-
-
- )}
- {nteeCategory && (
-
- Category: {nteeCategory}
- {
- setNteeCategory('')
- setTimeout(() => handleSearch(), 0)
- }}
- className="hover:bg-green-200 rounded-full p-0.5"
- >
-
-
-
- )}
-
- )}
-
- {/* Advanced Filters Panel */}
- {showFilters && (
-
-
- {/* State Filter */}
-
-
- State
-
- {
- setSelectedState(e.target.value)
- setCurrentPage(1)
- setTimeout(() => handleSearch(), 0)
- }}
- className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 text-gray-900 bg-white"
- >
- All States
- Alabama
- Georgia
- Massachusetts
- Washington
- Wisconsin
-
-
-
- {/* Sort By */}
-
-
- Sort By
-
- {
- setSortBy(e.target.value)
- setCurrentPage(1)
- setTimeout(() => handleSearch(), 0)
- }}
- className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 text-gray-900 bg-white"
- >
- Relevance
- Name (A-Z)
- Name (Z-A)
- Revenue (High to Low)
- Revenue (Low to High)
- Assets (High to Low)
- Assets (Low to High)
-
-
-
- {/* NTEE Category (for organizations) */}
-
-
- Category (NTEE)
-
- {
- setNteeCategory(e.target.value)
- setCurrentPage(1)
- setTimeout(() => handleSearch(), 0)
- }}
- className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 text-gray-900 bg-white"
- disabled={!selectedTypes.includes('organizations')}
- >
- All Categories
- Arts & Culture
- Education
- Environment
- Animal-Related
- Health
- Mental Health
- Diseases
- Medical Research
- Crime & Legal
- Employment
- Food & Agriculture
- Housing
- Public Safety
- Recreation & Sports
- Youth Development
- Human Services
- International
- Civil Rights
- Community
- Philanthropy
- Science
- Social Science
- Public Affairs
- Religion
- Mutual Benefit
-
-
-
-
- {/* Clear All Button */}
-
- {
- setSelectedState('')
- setSortBy('relevance')
- setNteeCategory('')
- setTimeout(() => handleSearch(), 0)
- }}
- className="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 transition-colors"
- >
- Clear All Filters
-
-
-
- )}
-
-
- {/* Search Results */}
- {(activeQuery || selectedState || searchResults) && (
-
- {isSearching && (
-
- )}
-
- {error && (
-
-
Error loading search results. Please try again.
-
- )}
-
- {searchResults && searchResults.total_results !== undefined && searchResults.pagination && (
- <>
- {/* Results Summary */}
-
-
- {searchResults.query ? (
- <>
- {searchResults.total_results.toLocaleString()} results for "{searchResults.query}"
- {searchResults.total_results > 0 && (
-
- (showing {searchResults.pagination.offset + 1}-
- {Math.min(searchResults.pagination.offset + searchResults.pagination.limit, searchResults.total_results)})
-
- )}
- >
- ) : (
- <>
- {searchResults.total_results.toLocaleString()} results
- {searchResults.total_results > 0 && (
-
- (showing {searchResults.pagination.offset + 1}-
- {Math.min(searchResults.pagination.offset + searchResults.pagination.limit, searchResults.total_results)})
-
- )}
- >
- )}
-
- {selectedState && (
-
- Filtered by state: {selectedState}
-
- )}
-
-
- {/* Jurisdiction Details Breakdown */}
- {jurisdictionDetails.length > 0 && (
-
-
-
- Your Jurisdictions
-
-
- When you select a city, you're connected to {jurisdictionDetails.length} levels of government:
-
-
- {jurisdictionDetails.map((item: any, index: number) => {
- const isExpanded = expandedJurisdictions.has(index)
- return (
-
- {/* Collapsed Header - Always Visible */}
-
toggleJurisdictionExpansion(index)}
- className="w-full flex items-center justify-between p-4 text-left hover:bg-gray-50 transition-colors"
- >
-
-
- {item.type}
- •
- {item.name}
-
- {!isExpanded && (
-
- Click to view details and discover data sources
-
- )}
-
-
-
- {isExpanded ? (
-
- ) : (
-
- )}
-
-
-
- {/* Expanded Details */}
- {isExpanded && (
-
-
- {/* Jurisdiction Info */}
-
-
-
- {item.count !== undefined && (
-
-
Count
-
{item.count.toLocaleString()}
-
- )}
-
-
- {/* Discover Data Sources Button */}
-
-
-
-
-
-
-
-
- Automated Data Discovery
-
-
- Automatically find official websites, meeting agendas, YouTube channels, and social media for this jurisdiction.
-
-
{
- e.stopPropagation()
- // Navigate to discovery page
- const searchQuery = `${item.name} ${item.type}`
- navigate(`/discovery?q=${encodeURIComponent(searchQuery)}&jurisdiction=${encodeURIComponent(item.name)}`)
- }}
- className="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 transition-colors"
- >
-
- Discover Data Sources
-
-
-
-
-
-
- {/* What We'll Find */}
-
-
- What We'll Discover
-
-
-
-
- Official Website
-
-
-
- Meeting Agendas
-
-
-
- YouTube Channels
-
-
-
- Social Media
-
-
-
-
-
- )}
-
- )
- })}
-
-
-
- 💡 Why this matters: Each jurisdiction has its own meetings, budgets, and leaders that affect your daily life. Track all of them in one place.
-
-
-
- )}
-
- {/* Results by Type */}
- {selectedTypes.includes('contacts') && searchResults.results?.contacts && searchResults.results.contacts.length > 0 && (
-
-
-
- People ({searchResults.results.contacts.length})
-
-
- {searchResults.results.contacts.map((result, idx) => (
-
- ))}
-
-
- )}
-
- {selectedTypes.includes('meetings') && searchResults.results?.meetings && searchResults.results.meetings.length > 0 && (
-
-
-
- Meetings ({searchResults.results.meetings.length})
-
-
- {searchResults.results.meetings.map((result, idx) => (
-
- ))}
-
-
- )}
-
- {selectedTypes.includes('organizations') && searchResults.results?.organizations && searchResults.results.organizations.length > 0 && (
-
-
-
- Organizations ({searchResults.results.organizations.length})
-
-
- {searchResults.results.organizations.map((result, idx) => (
-
- ))}
-
-
- )}
-
- {selectedTypes.includes('causes') && searchResults.results?.causes && searchResults.results.causes.length > 0 && (
-
-
-
- Causes ({searchResults.results.causes.length})
-
-
- {searchResults.results.causes.map((result, idx) => (
-
- ))}
-
-
- )}
-
- {selectedTypes.includes('jurisdictions') && searchResults.results?.jurisdictions && searchResults.results.jurisdictions.length > 0 && (
-
-
-
- Jurisdictions ({searchResults.results.jurisdictions.length})
-
-
- {searchResults.results.jurisdictions.map((result, idx) => (
-
- ))}
-
-
- )}
-
- {/* No Results */}
- {searchResults.total_results === 0 && (
-
-
-
No results found
-
- Try different keywords or adjust your filters
-
-
- )}
-
- {/* Pagination Controls */}
- {searchResults.total_results > 0 && searchResults.pagination.total_pages > 1 && (
-
-
- Page {searchResults.pagination.page} of {searchResults.pagination.total_pages}
- •
- {searchResults.total_results} total results
-
-
-
-
handlePageChange(searchResults.pagination.page - 1)}
- disabled={!searchResults.pagination.has_prev}
- className={`px-4 py-2 rounded-lg font-medium transition-colors ${
- searchResults.pagination.has_prev
- ? 'bg-primary-600 text-white hover:bg-primary-700'
- : 'bg-gray-100 text-gray-400 cursor-not-allowed'
- }`}
- >
- ← Previous
-
-
- {/* Page numbers */}
-
- {Array.from({ length: Math.min(5, searchResults.pagination.total_pages) }, (_, i) => {
- const pageNum = Math.max(
- 1,
- Math.min(
- searchResults.pagination.page - 2 + i,
- searchResults.pagination.total_pages - 4
- )
- ) + Math.min(i, 4)
-
- if (pageNum > searchResults.pagination.total_pages) return null
-
- return (
- handlePageChange(pageNum)}
- className={`px-3 py-1 rounded ${
- pageNum === searchResults.pagination.page
- ? 'bg-primary-600 text-white font-semibold'
- : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
- }`}
- >
- {pageNum}
-
- )
- })}
-
-
-
handlePageChange(searchResults.pagination.page + 1)}
- disabled={!searchResults.pagination.has_next}
- className={`px-4 py-2 rounded-lg font-medium transition-colors ${
- searchResults.pagination.has_next
- ? 'bg-primary-600 text-white hover:bg-primary-700'
- : 'bg-gray-100 text-gray-400 cursor-not-allowed'
- }`}
- >
- Next →
-
-
-
- )}
- >
- )}
-
- )}
-
- {/* Initial State - Search Examples */}
- {!activeQuery && (
-
- {(selectedState || selectedTypes.length < 5) && (
-
-
- {selectedState && `State filter: ${selectedState}`}
- {selectedState && selectedTypes.length < 5 && ' • '}
- {selectedTypes.length < 5 && `Type filter: ${selectedTypes.join(', ')}`}
-
-
- Enter a search query above to see results with these filters applied.
-
-
- )}
-
Try searching for:
-
- {[
- { query: 'dental health', icon: HeartIcon, description: 'Find organizations and meetings about dental health' },
- { query: 'affordable housing', icon: BuildingOfficeIcon, description: 'Discover housing-related initiatives' },
- { query: 'school board', icon: CalendarIcon, description: 'View school board meetings and decisions' },
- { query: 'mental health', icon: HeartIcon, description: 'Explore mental health programs and services' },
- ].map((example, idx) => (
-
{
- setQuery(example.query)
- setActiveQuery(example.query)
- }}
- className="text-left p-4 border-2 border-gray-200 rounded-lg hover:border-primary-500 hover:bg-primary-50 transition-colors"
- >
-
-
- {example.query}
-
- {example.description}
-
- ))}
-
-
- )}
-
-
- )
-}
diff --git a/frontend/src/utils/formatters.ts b/frontend/src/utils/formatters.ts
deleted file mode 100644
index a4e6e5f87b5bcd2e969682fbefaf79db8956223f..0000000000000000000000000000000000000000
--- a/frontend/src/utils/formatters.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-/**
- * Format a number as currency with intelligent units (K, M, B)
- * @param amount - The amount to format
- * @returns Formatted string like "$297.9M" or "$1.2B"
- */
-export const formatCurrency = (amount: number | undefined | null): string => {
- if (!amount || amount === 0) return '$0'
-
- const absAmount = Math.abs(amount)
-
- if (absAmount >= 1_000_000_000) {
- return `$${(amount / 1_000_000_000).toFixed(1)}B`
- } else if (absAmount >= 1_000_000) {
- return `$${(amount / 1_000_000).toFixed(1)}M`
- } else if (absAmount >= 1_000) {
- return `$${(amount / 1_000).toFixed(1)}K`
- } else {
- return `$${amount.toFixed(0)}`
- }
-}
-
-/**
- * Format a number with intelligent units (K, M, B) without currency symbol
- * @param num - The number to format
- * @returns Formatted string like "297.9M" or "1.2B"
- */
-export const formatNumber = (num: number | undefined | null): string => {
- if (!num || num === 0) return '0'
-
- const absNum = Math.abs(num)
-
- if (absNum >= 1_000_000_000) {
- return `${(num / 1_000_000_000).toFixed(1)}B`
- } else if (absNum >= 1_000_000) {
- return `${(num / 1_000_000).toFixed(1)}M`
- } else if (absNum >= 1_000) {
- return `${(num / 1_000).toFixed(1)}K`
- } else {
- return num.toLocaleString()
- }
-}
diff --git a/frontend/src/utils/huggingface.ts b/frontend/src/utils/huggingface.ts
deleted file mode 100644
index 6c794e91ef70129d9aeef045998244bd1ad0a77d..0000000000000000000000000000000000000000
--- a/frontend/src/utils/huggingface.ts
+++ /dev/null
@@ -1,302 +0,0 @@
-/**
- * HuggingFace Datasets Server API Client
- *
- * Query datasets hosted on HuggingFace Hub using the free Datasets Server API.
- * No authentication required for public datasets!
- *
- * API Docs: https://huggingface.co/docs/datasets-server
- */
-
-const DATASETS_SERVER_URL = 'https://datasets-server.huggingface.co';
-
-export interface HFDatasetConfig {
- dataset: string; // e.g., "CommunityOne/oral-health-nonprofits"
- config?: string; // Default: "default"
- split: string; // e.g., "organizations", "financials"
-}
-
-export interface HFRow {
- row_idx: number;
- row: Record;
- truncated_cells: string[];
-}
-
-export interface HFRowsResponse {
- features: Array<{
- feature_idx: number;
- name: string;
- type: any;
- }>;
- rows: HFRow[];
- num_rows_total: number;
- num_rows_per_page: number;
- partial: boolean;
-}
-
-export interface HFSearchResponse {
- features: Array<{
- feature_idx: number;
- name: string;
- type: any;
- }>;
- rows: HFRow[];
- num_rows_total: number;
- num_rows_per_page: number;
- partial: boolean;
- truncated: boolean;
-}
-
-/**
- * Fetch rows from a HuggingFace dataset
- *
- * @param config - Dataset configuration
- * @param offset - Starting row (default: 0)
- * @param length - Number of rows to fetch (max: 100)
- * @returns Promise with dataset rows
- *
- * @example
- * const nonprofits = await fetchHFRows({
- * dataset: "CommunityOne/oral-health-nonprofits",
- * split: "organizations"
- * }, 0, 100);
- */
-export async function fetchHFRows(
- config: HFDatasetConfig,
- offset = 0,
- length = 100
-): Promise {
- const params = new URLSearchParams({
- dataset: config.dataset,
- config: config.config || 'default',
- split: config.split,
- offset: offset.toString(),
- length: Math.min(length, 100).toString() // API max is 100
- });
-
- const url = `${DATASETS_SERVER_URL}/rows?${params}`;
-
- const response = await fetch(url);
-
- if (!response.ok) {
- throw new Error(`HuggingFace API error: ${response.statusText}`);
- }
-
- return response.json();
-}
-
-/**
- * Search within a HuggingFace dataset
- *
- * @param config - Dataset configuration
- * @param query - Search query string
- * @param offset - Starting row (default: 0)
- * @param length - Number of results (max: 100)
- * @returns Promise with search results
- *
- * @example
- * const dentalOrgs = await searchHFDataset({
- * dataset: "CommunityOne/oral-health-nonprofits",
- * split: "organizations"
- * }, "dental");
- */
-export async function searchHFDataset(
- config: HFDatasetConfig,
- query: string,
- offset = 0,
- length = 100
-): Promise {
- const params = new URLSearchParams({
- dataset: config.dataset,
- config: config.config || 'default',
- split: config.split,
- query: query,
- offset: offset.toString(),
- length: Math.min(length, 100).toString()
- });
-
- const url = `${DATASETS_SERVER_URL}/search?${params}`;
-
- const response = await fetch(url);
-
- if (!response.ok) {
- throw new Error(`HuggingFace search error: ${response.statusText}`);
- }
-
- return response.json();
-}
-
-/**
- * Get dataset size (number of rows)
- *
- * @param config - Dataset configuration
- * @returns Promise with dataset size info
- */
-export async function getHFDatasetSize(config: HFDatasetConfig): Promise<{
- dataset: string;
- config: string;
- split: string;
- num_rows_total: number;
- num_rows_per_page: number;
- partial: boolean;
-}> {
- const params = new URLSearchParams({
- dataset: config.dataset,
- config: config.config || 'default',
- split: config.split
- });
-
- const url = `${DATASETS_SERVER_URL}/size?${params}`;
-
- const response = await fetch(url);
-
- if (!response.ok) {
- throw new Error(`HuggingFace API error: ${response.statusText}`);
- }
-
- return response.json();
-}
-
-/**
- * Fetch all nonprofits (paginated helper)
- *
- * @param dataset - HuggingFace dataset name
- * @param split - Dataset split (default: "organizations")
- * @param maxRows - Maximum total rows to fetch
- * @returns Promise with all rows combined
- *
- * @example
- * const allNonprofits = await fetchAllNonprofits(
- * "CommunityOne/oral-health-nonprofits",
- * "organizations",
- * 1000
- * );
- */
-export async function fetchAllNonprofits(
- dataset: string,
- split = 'organizations',
- maxRows = 1000
-): Promise {
- const allRows: any[] = [];
- let offset = 0;
- const batchSize = 100; // API max per request
-
- while (offset < maxRows) {
- const response = await fetchHFRows(
- { dataset, split },
- offset,
- Math.min(batchSize, maxRows - offset)
- );
-
- const rows = response.rows.map(r => r.row);
- allRows.push(...rows);
-
- // Stop if we've fetched all available rows
- if (rows.length < batchSize) {
- break;
- }
-
- offset += batchSize;
- }
-
- return allRows;
-}
-
-/**
- * Filter nonprofits by state
- *
- * @param dataset - HuggingFace dataset name
- * @param stateCode - Two-letter state code (e.g., "AL", "CA")
- * @param maxRows - Maximum rows to fetch
- * @returns Promise with filtered nonprofits
- *
- * @example
- * const alabamaNonprofits = await fetchNonprofitsByState(
- * "CommunityOne/one-nonprofits-organizations",
- * "AL",
- * 5000
- * );
- */
-export async function fetchNonprofitsByState(
- dataset: string,
- stateCode: string,
- maxRows = 5000
-): Promise {
- const allRows = await fetchAllNonprofits(dataset, 'organizations', maxRows);
- return allRows.filter(row => row.state === stateCode);
-}
-
-/**
- * Filter nonprofits by NTEE code
- *
- * @param dataset - HuggingFace dataset name
- * @param nteePrefix - NTEE code prefix (e.g., "E" for health, "X" for religion)
- * @param maxRows - Maximum rows to fetch
- * @returns Promise with filtered nonprofits
- *
- * @example
- * const healthOrgs = await fetchNonprofitsByNTEE(
- * "CommunityOne/one-nonprofits-organizations",
- * "E",
- * 10000
- * );
- */
-export async function fetchNonprofitsByNTEE(
- dataset: string,
- nteePrefix: string,
- maxRows = 10000
-): Promise {
- const allRows = await fetchAllNonprofits(dataset, 'organizations', maxRows);
- return allRows.filter(row => row.ntee_code?.startsWith(nteePrefix));
-}
-
-/**
- * Nonprofit search with filters
- *
- * @param options - Search options
- * @returns Promise with search results
- *
- * @example
- * const results = await searchNonprofits({
- * dataset: "CommunityOne/one-nonprofits-organizations",
- * query: "dental",
- * state: "CA",
- * nteeCode: "E",
- * limit: 100
- * });
- */
-export async function searchNonprofits(options: {
- dataset: string;
- query?: string;
- state?: string;
- nteeCode?: string;
- limit?: number;
-}): Promise {
- const { dataset, query, state, nteeCode, limit = 100 } = options;
-
- let results: any[];
-
- if (query) {
- // Use HuggingFace search API
- const response = await searchHFDataset(
- { dataset, split: 'organizations' },
- query,
- 0,
- limit
- );
- results = response.rows.map(r => r.row);
- } else {
- // Fetch and filter
- results = await fetchAllNonprofits(dataset, 'organizations', limit);
- }
-
- // Apply filters
- if (state) {
- results = results.filter(row => row.state === state);
- }
-
- if (nteeCode) {
- results = results.filter(row => row.ntee_code?.startsWith(nteeCode));
- }
-
- return results.slice(0, limit);
-}
diff --git a/frontend/src/utils/stateMapping.ts b/frontend/src/utils/stateMapping.ts
deleted file mode 100644
index f47739912498527705b24dd65930d3efa87a8742..0000000000000000000000000000000000000000
--- a/frontend/src/utils/stateMapping.ts
+++ /dev/null
@@ -1,87 +0,0 @@
-/**
- * US State name to 2-letter code mapping
- */
-export const STATE_NAME_TO_CODE: Record = {
- 'Alabama': 'AL',
- 'Alaska': 'AK',
- 'Arizona': 'AZ',
- 'Arkansas': 'AR',
- 'California': 'CA',
- 'Colorado': 'CO',
- 'Connecticut': 'CT',
- 'Delaware': 'DE',
- 'Florida': 'FL',
- 'Georgia': 'GA',
- 'Hawaii': 'HI',
- 'Idaho': 'ID',
- 'Illinois': 'IL',
- 'Indiana': 'IN',
- 'Iowa': 'IA',
- 'Kansas': 'KS',
- 'Kentucky': 'KY',
- 'Louisiana': 'LA',
- 'Maine': 'ME',
- 'Maryland': 'MD',
- 'Massachusetts': 'MA',
- 'Michigan': 'MI',
- 'Minnesota': 'MN',
- 'Mississippi': 'MS',
- 'Missouri': 'MO',
- 'Montana': 'MT',
- 'Nebraska': 'NE',
- 'Nevada': 'NV',
- 'New Hampshire': 'NH',
- 'New Jersey': 'NJ',
- 'New Mexico': 'NM',
- 'New York': 'NY',
- 'North Carolina': 'NC',
- 'North Dakota': 'ND',
- 'Ohio': 'OH',
- 'Oklahoma': 'OK',
- 'Oregon': 'OR',
- 'Pennsylvania': 'PA',
- 'Rhode Island': 'RI',
- 'South Carolina': 'SC',
- 'South Dakota': 'SD',
- 'Tennessee': 'TN',
- 'Texas': 'TX',
- 'Utah': 'UT',
- 'Vermont': 'VT',
- 'Virginia': 'VA',
- 'Washington': 'WA',
- 'West Virginia': 'WV',
- 'Wisconsin': 'WI',
- 'Wyoming': 'WY',
- 'District of Columbia': 'DC',
- 'Puerto Rico': 'PR'
-}
-
-/**
- * Convert a full state name to its 2-letter code
- * @param stateName - Full state name (e.g., "Massachusetts")
- * @returns 2-letter state code (e.g., "MA") or the original string if not found
- */
-export function stateNameToCode(stateName: string): string {
- // If it's already a 2-letter code, return as-is
- if (stateName && stateName.length === 2 && stateName === stateName.toUpperCase()) {
- return stateName
- }
-
- // Try exact match
- if (STATE_NAME_TO_CODE[stateName]) {
- return STATE_NAME_TO_CODE[stateName]
- }
-
- // Try case-insensitive match
- const normalizedName = Object.keys(STATE_NAME_TO_CODE).find(
- key => key.toLowerCase() === stateName.toLowerCase()
- )
-
- if (normalizedName) {
- return STATE_NAME_TO_CODE[normalizedName]
- }
-
- // Return original if not found
- console.warn(`State name "${stateName}" not found in mapping`)
- return stateName
-}
diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts
deleted file mode 100644
index 0437824900af6705dfcc3aa92ce828e4c7a3a515..0000000000000000000000000000000000000000
--- a/frontend/src/vite-env.d.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-///
-
-interface ImportMetaEnv {
- readonly VITE_APP_TITLE: string
- // more env variables...
-}
-
-interface ImportMeta {
- readonly env: ImportMetaEnv
-}
diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js
deleted file mode 100644
index 1e47aa5a11489d9369069083ab9b52ece1fa25a7..0000000000000000000000000000000000000000
--- a/frontend/tailwind.config.js
+++ /dev/null
@@ -1,37 +0,0 @@
-/** @type {import('tailwindcss').Config} */
-export default {
- content: [
- "./index.html",
- "./src/**/*.{js,ts,jsx,tsx}",
- ],
- theme: {
- extend: {
- colors: {
- primary: {
- 50: '#e8eaeb',
- 100: '#c5cace',
- 500: '#354F52',
- 600: '#2e4346',
- 700: '#27383a',
- },
- sky: {
- 50: '#e8eaeb',
- 100: '#c5cace',
- 500: '#354F52',
- 600: '#2e4346',
- 700: '#27383a',
- },
- neutral: {
- 600: '#354F52',
- 700: '#2e4346',
- },
- slate: {
- 500: '#64748B',
- 600: '#475569',
- 700: '#334155',
- },
- },
- },
- },
- plugins: [],
-}
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
deleted file mode 100644
index 20a1c3c697cf54f0d3692c03f3b648e59d466920..0000000000000000000000000000000000000000
--- a/frontend/vite.config.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { defineConfig } from 'vite'
-import react from '@vitejs/plugin-react'
-import path from 'path'
-
-// https://vitejs.dev/config/
-export default defineConfig({
- plugins: [react()],
- resolve: {
- alias: {
- '@': path.resolve(__dirname, './src'),
- },
- },
- server: {
- port: 5173,
- proxy: {
- '/api': {
- target: 'http://localhost:8000',
- changeOrigin: true,
- followRedirects: true,
- configure: (proxy, _options) => {
- proxy.on('error', (err, _req, _res) => {
- console.log('proxy error', err);
- });
- proxy.on('proxyReq', (proxyReq, req, _res) => {
- console.log('Proxying:', req.method, req.url, '→', proxyReq.path);
- });
- },
- },
- },
- },
- build: {
- outDir: '../api/static',
- emptyOutDir: true,
- },
-})
diff --git a/index.html b/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..f38b5f9e6732e4664327b0ad551e30e3934cc057
--- /dev/null
+++ b/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ CommunityOne - The open path to everything local
+
+
+
+
+
+
diff --git a/frontend/package-lock.json b/package-lock.json
similarity index 50%
rename from frontend/package-lock.json
rename to package-lock.json
index 925d7eb50fdc8fe2f7c051ae729b32203dc81237..f8067c25bac6f93c08b22dd15fe2bbb19926fd7f 100644
--- a/frontend/package-lock.json
+++ b/package-lock.json
@@ -1,39 +1,23 @@
{
- "name": "oral-health-policy-pulse-ui",
+ "name": "communityone-org-card",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "oral-health-policy-pulse-ui",
+ "name": "communityone-org-card",
"version": "1.0.0",
"dependencies": {
"@headlessui/react": "^1.7.17",
"@heroicons/react": "^2.0.18",
- "@tanstack/react-query": "^5.14.2",
- "axios": "^1.6.2",
- "clsx": "^2.0.0",
- "date-fns": "^2.30.0",
- "leaflet": "^1.9.4",
"react": "^18.2.0",
- "react-dom": "^18.2.0",
- "react-leaflet": "^4.2.1",
- "react-query": "^3.39.3",
- "react-router-dom": "^6.20.0",
- "recharts": "^2.10.3",
- "zustand": "^4.4.7"
+ "react-dom": "^18.2.0"
},
"devDependencies": {
- "@types/leaflet": "^1.9.8",
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
- "@typescript-eslint/eslint-plugin": "^6.14.0",
- "@typescript-eslint/parser": "^6.14.0",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.16",
- "eslint": "^8.55.0",
- "eslint-plugin-react-hooks": "^4.6.0",
- "eslint-plugin-react-refresh": "^0.4.5",
"postcss": "^8.4.32",
"tailwindcss": "^3.3.6",
"typescript": "^5.2.2",
@@ -109,16 +93,6 @@
"url": "https://opencollective.com/babel"
}
},
- "node_modules/@babel/core/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
"node_modules/@babel/generator": {
"version": "7.29.1",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
@@ -153,16 +127,6 @@
"node": ">=6.9.0"
}
},
- "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
"node_modules/@babel/helper-globals": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
@@ -307,15 +271,6 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/runtime": {
- "version": "7.29.2",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
- "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
"node_modules/@babel/template": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
@@ -755,93 +710,6 @@
"node": ">=12"
}
},
- "node_modules/@eslint-community/eslint-utils": {
- "version": "4.9.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
- "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eslint-visitor-keys": "^3.4.3"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- },
- "peerDependencies": {
- "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
- }
- },
- "node_modules/@eslint-community/regexpp": {
- "version": "4.12.2",
- "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
- "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
- }
- },
- "node_modules/@eslint/eslintrc": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
- "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ajv": "^6.12.4",
- "debug": "^4.3.2",
- "espree": "^9.6.0",
- "globals": "^13.19.0",
- "ignore": "^5.2.0",
- "import-fresh": "^3.2.1",
- "js-yaml": "^4.1.0",
- "minimatch": "^3.1.2",
- "strip-json-comments": "^3.1.1"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
- "version": "1.1.14",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
- "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/@eslint/eslintrc/node_modules/minimatch": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
- "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/@eslint/js": {
- "version": "8.57.1",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
- "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- }
- },
"node_modules/@headlessui/react": {
"version": "1.7.19",
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.19.tgz",
@@ -868,68 +736,6 @@
"react": ">= 16 || ^19.0.0-rc"
}
},
- "node_modules/@humanwhocodes/config-array": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
- "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
- "deprecated": "Use @eslint/config-array instead",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@humanwhocodes/object-schema": "^2.0.3",
- "debug": "^4.3.1",
- "minimatch": "^3.0.5"
- },
- "engines": {
- "node": ">=10.10.0"
- }
- },
- "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
- "version": "1.1.14",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
- "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/@humanwhocodes/config-array/node_modules/minimatch": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
- "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/@humanwhocodes/module-importer": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
- "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=12.22"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
- "node_modules/@humanwhocodes/object-schema": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
- "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
- "deprecated": "Use @eslint/object-schema instead",
- "dev": true,
- "license": "BSD-3-Clause"
- },
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@@ -1018,26 +824,6 @@
"node": ">= 8"
}
},
- "node_modules/@react-leaflet/core": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-2.1.0.tgz",
- "integrity": "sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg==",
- "license": "Hippocratic-2.1",
- "peerDependencies": {
- "leaflet": "^1.9.0",
- "react": "^18.0.0",
- "react-dom": "^18.0.0"
- }
- },
- "node_modules/@remix-run/router": {
- "version": "1.23.2",
- "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz",
- "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==",
- "license": "MIT",
- "engines": {
- "node": ">=14.0.0"
- }
- },
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.27",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
@@ -1395,32 +1181,6 @@
"win32"
]
},
- "node_modules/@tanstack/query-core": {
- "version": "5.100.3",
- "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.3.tgz",
- "integrity": "sha512-oMO1imV4qStH+GqddafkI7Q7r2ktPL7/0Mu74W1XEhfHHd3oTIrwP3OOIsbtpnnbe8y/IU+8Lm7Bi2LlMhVdNA==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/tannerlinsley"
- }
- },
- "node_modules/@tanstack/react-query": {
- "version": "5.100.3",
- "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.3.tgz",
- "integrity": "sha512-8Fgb4vKmBHllRHUjz3ZOwgV0v9b7cxCdN5T0iFQvvWJJVs6xvaxHERO1BclTL03bbK8vZAuXVKN3IeVS1sUdeQ==",
- "license": "MIT",
- "dependencies": {
- "@tanstack/query-core": "5.100.3"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/tannerlinsley"
- },
- "peerDependencies": {
- "react": "^18 || ^19"
- }
- },
"node_modules/@tanstack/react-virtual": {
"version": "3.13.24",
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.24.tgz",
@@ -1493,69 +1253,6 @@
"@babel/types": "^7.28.2"
}
},
- "node_modules/@types/d3-array": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
- "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
- "license": "MIT"
- },
- "node_modules/@types/d3-color": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
- "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
- "license": "MIT"
- },
- "node_modules/@types/d3-ease": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
- "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
- "license": "MIT"
- },
- "node_modules/@types/d3-interpolate": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
- "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
- "license": "MIT",
- "dependencies": {
- "@types/d3-color": "*"
- }
- },
- "node_modules/@types/d3-path": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
- "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
- "license": "MIT"
- },
- "node_modules/@types/d3-scale": {
- "version": "4.0.9",
- "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
- "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
- "license": "MIT",
- "dependencies": {
- "@types/d3-time": "*"
- }
- },
- "node_modules/@types/d3-shape": {
- "version": "3.1.8",
- "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz",
- "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==",
- "license": "MIT",
- "dependencies": {
- "@types/d3-path": "*"
- }
- },
- "node_modules/@types/d3-time": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
- "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
- "license": "MIT"
- },
- "node_modules/@types/d3-timer": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
- "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
- "license": "MIT"
- },
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -1563,42 +1260,18 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@types/geojson": {
- "version": "7946.0.16",
- "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
- "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/json-schema": {
- "version": "7.0.15",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
- "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/leaflet": {
- "version": "1.9.21",
- "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.21.tgz",
- "integrity": "sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/geojson": "*"
- }
- },
"node_modules/@types/prop-types": {
"version": "15.7.15",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
- "devOptional": true,
+ "dev": true,
"license": "MIT"
},
"node_modules/@types/react": {
"version": "18.3.28",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
"integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
@@ -1615,414 +1288,96 @@
"@types/react": "^18.0.0"
}
},
- "node_modules/@types/semver": {
- "version": "7.7.1",
- "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz",
- "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@typescript-eslint/eslint-plugin": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz",
- "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==",
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@eslint-community/regexpp": "^4.5.1",
- "@typescript-eslint/scope-manager": "6.21.0",
- "@typescript-eslint/type-utils": "6.21.0",
- "@typescript-eslint/utils": "6.21.0",
- "@typescript-eslint/visitor-keys": "6.21.0",
- "debug": "^4.3.4",
- "graphemer": "^1.4.0",
- "ignore": "^5.2.4",
- "natural-compare": "^1.4.0",
- "semver": "^7.5.4",
- "ts-api-utils": "^1.0.1"
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
},
"engines": {
- "node": "^16.0.0 || >=18.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
+ "node": "^14.18.0 || >=16.0.0"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha",
- "eslint": "^7.0.0 || ^8.0.0"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
}
},
- "node_modules/@typescript-eslint/parser": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz",
- "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==",
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
"dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "@typescript-eslint/scope-manager": "6.21.0",
- "@typescript-eslint/types": "6.21.0",
- "@typescript-eslint/typescript-estree": "6.21.0",
- "@typescript-eslint/visitor-keys": "6.21.0",
- "debug": "^4.3.4"
- },
- "engines": {
- "node": "^16.0.0 || >=18.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^7.0.0 || ^8.0.0"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
- }
+ "license": "MIT"
},
- "node_modules/@typescript-eslint/scope-manager": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz",
- "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==",
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
- "license": "MIT",
+ "license": "ISC",
"dependencies": {
- "@typescript-eslint/types": "6.21.0",
- "@typescript-eslint/visitor-keys": "6.21.0"
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
},
"engines": {
- "node": "^16.0.0 || >=18.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
+ "node": ">= 8"
}
},
- "node_modules/@typescript-eslint/type-utils": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz",
- "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==",
+ "node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/autoprefixer": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz",
+ "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==",
"dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
"license": "MIT",
"dependencies": {
- "@typescript-eslint/typescript-estree": "6.21.0",
- "@typescript-eslint/utils": "6.21.0",
- "debug": "^4.3.4",
- "ts-api-utils": "^1.0.1"
+ "browserslist": "^4.28.2",
+ "caniuse-lite": "^1.0.30001787",
+ "fraction.js": "^5.3.4",
+ "picocolors": "^1.1.1",
+ "postcss-value-parser": "^4.2.0"
},
- "engines": {
- "node": "^16.0.0 || >=18.0.0"
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
},
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
+ "engines": {
+ "node": "^10 || ^12 || >=14"
},
"peerDependencies": {
- "eslint": "^7.0.0 || ^8.0.0"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
+ "postcss": "^8.1.0"
}
},
- "node_modules/@typescript-eslint/types": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz",
- "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^16.0.0 || >=18.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz",
- "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "@typescript-eslint/types": "6.21.0",
- "@typescript-eslint/visitor-keys": "6.21.0",
- "debug": "^4.3.4",
- "globby": "^11.1.0",
- "is-glob": "^4.0.3",
- "minimatch": "9.0.3",
- "semver": "^7.5.4",
- "ts-api-utils": "^1.0.1"
- },
- "engines": {
- "node": "^16.0.0 || >=18.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
- }
- },
- "node_modules/@typescript-eslint/utils": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz",
- "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.4.0",
- "@types/json-schema": "^7.0.12",
- "@types/semver": "^7.5.0",
- "@typescript-eslint/scope-manager": "6.21.0",
- "@typescript-eslint/types": "6.21.0",
- "@typescript-eslint/typescript-estree": "6.21.0",
- "semver": "^7.5.4"
- },
- "engines": {
- "node": "^16.0.0 || >=18.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^7.0.0 || ^8.0.0"
- }
- },
- "node_modules/@typescript-eslint/visitor-keys": {
- "version": "6.21.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz",
- "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "6.21.0",
- "eslint-visitor-keys": "^3.4.1"
- },
- "engines": {
- "node": "^16.0.0 || >=18.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@ungap/structured-clone": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
- "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/@vitejs/plugin-react": {
- "version": "4.7.0",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
- "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/core": "^7.28.0",
- "@babel/plugin-transform-react-jsx-self": "^7.27.1",
- "@babel/plugin-transform-react-jsx-source": "^7.27.1",
- "@rolldown/pluginutils": "1.0.0-beta.27",
- "@types/babel__core": "^7.20.5",
- "react-refresh": "^0.17.0"
- },
- "engines": {
- "node": "^14.18.0 || >=16.0.0"
- },
- "peerDependencies": {
- "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
- }
- },
- "node_modules/acorn": {
- "version": "8.16.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
- "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/acorn-jsx": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
- "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
- }
- },
- "node_modules/ajv": {
- "version": "6.15.0",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
- "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/any-promise": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
- "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/anymatch": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
- "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "normalize-path": "^3.0.0",
- "picomatch": "^2.0.4"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/arg": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
- "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true,
- "license": "Python-2.0"
- },
- "node_modules/array-union": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
- "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/asynckit": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
- "license": "MIT"
- },
- "node_modules/autoprefixer": {
- "version": "10.5.0",
- "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz",
- "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/autoprefixer"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "browserslist": "^4.28.2",
- "caniuse-lite": "^1.0.30001787",
- "fraction.js": "^5.3.4",
- "picocolors": "^1.1.1",
- "postcss-value-parser": "^4.2.0"
- },
- "bin": {
- "autoprefixer": "bin/autoprefixer"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- },
- "peerDependencies": {
- "postcss": "^8.1.0"
- }
- },
- "node_modules/axios": {
- "version": "1.15.2",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.2.tgz",
- "integrity": "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==",
- "license": "MIT",
- "dependencies": {
- "follow-redirects": "^1.15.11",
- "form-data": "^4.0.5",
- "proxy-from-env": "^2.1.0"
- }
- },
- "node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "license": "MIT"
- },
"node_modules/baseline-browser-mapping": {
- "version": "2.10.22",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.22.tgz",
- "integrity": "sha512-6qruVrb5rse6WylFkU0FhBKKGuecWseqdpQfhkawn6ztyk2QlfwSRjsDxMCLJrkfmfN21qvhl9ABgaMeRkuwww==",
+ "version": "2.10.24",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.24.tgz",
+ "integrity": "sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -2032,15 +1387,6 @@
"node": ">=6.0.0"
}
},
- "node_modules/big-integer": {
- "version": "1.6.52",
- "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
- "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==",
- "license": "Unlicense",
- "engines": {
- "node": ">=0.6"
- }
- },
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -2054,16 +1400,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/brace-expansion": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
- "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
@@ -2077,22 +1413,6 @@
"node": ">=8"
}
},
- "node_modules/broadcast-channel": {
- "version": "3.7.0",
- "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz",
- "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.7.2",
- "detect-node": "^2.1.0",
- "js-sha3": "0.8.0",
- "microseconds": "0.2.0",
- "nano-time": "1.0.0",
- "oblivious-set": "1.0.0",
- "rimraf": "3.0.2",
- "unload": "2.2.0"
- }
- },
"node_modules/browserslist": {
"version": "4.28.2",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
@@ -2127,29 +1447,6 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
- "node_modules/call-bind-apply-helpers": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
- "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/callsites": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
- "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/camelcase-css": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
@@ -2161,9 +1458,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001790",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001790.tgz",
- "integrity": "sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==",
+ "version": "1.0.30001791",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz",
+ "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==",
"dev": true,
"funding": [
{
@@ -2181,23 +1478,6 @@
],
"license": "CC-BY-4.0"
},
- "node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -2242,47 +1522,6 @@
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT"
},
- "node_modules/clsx": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
- "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/combined-stream": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
- "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
- "license": "MIT",
- "dependencies": {
- "delayed-stream": "~1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/commander": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
@@ -2293,12 +1532,6 @@
"node": ">= 6"
}
},
- "node_modules/concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
- "license": "MIT"
- },
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@@ -2306,21 +1539,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/cross-spawn": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
- "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "path-key": "^3.1.0",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
- },
- "engines": {
- "node": ">= 8"
- }
- },
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -2338,145 +1556,9 @@
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "dev": true,
"license": "MIT"
},
- "node_modules/d3-array": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
- "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
- "license": "ISC",
- "dependencies": {
- "internmap": "1 - 2"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-color": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
- "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
- "license": "ISC",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-ease": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
- "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-format": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz",
- "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==",
- "license": "ISC",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-interpolate": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
- "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
- "license": "ISC",
- "dependencies": {
- "d3-color": "1 - 3"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-path": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
- "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
- "license": "ISC",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-scale": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
- "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
- "license": "ISC",
- "dependencies": {
- "d3-array": "2.10.0 - 3",
- "d3-format": "1 - 3",
- "d3-interpolate": "1.2.0 - 3",
- "d3-time": "2.1.1 - 3",
- "d3-time-format": "2 - 4"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-shape": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
- "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
- "license": "ISC",
- "dependencies": {
- "d3-path": "^3.1.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-time": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
- "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
- "license": "ISC",
- "dependencies": {
- "d3-array": "2 - 3"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-time-format": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
- "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
- "license": "ISC",
- "dependencies": {
- "d3-time": "1 - 3"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-timer": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
- "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
- "license": "ISC",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/date-fns": {
- "version": "2.30.0",
- "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
- "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.21.0"
- },
- "engines": {
- "node": ">=0.11"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/date-fns"
- }
- },
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -2495,34 +1577,6 @@
}
}
},
- "node_modules/decimal.js-light": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
- "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
- "license": "MIT"
- },
- "node_modules/deep-is": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
- "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/delayed-stream": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
- "license": "MIT",
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/detect-node": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
- "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
- "license": "MIT"
- },
"node_modules/didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@@ -2530,19 +1584,6 @@
"dev": true,
"license": "Apache-2.0"
},
- "node_modules/dir-glob": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
- "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "path-type": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
@@ -2550,43 +1591,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/doctrine": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
- "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "esutils": "^2.0.2"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/dom-helpers": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
- "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.8.7",
- "csstype": "^3.0.2"
- }
- },
- "node_modules/dunder-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
- "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.1",
- "es-errors": "^1.3.0",
- "gopd": "^1.2.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
"node_modules/electron-to-chromium": {
"version": "1.5.344",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz",
@@ -2594,51 +1598,16 @@
"dev": true,
"license": "ISC"
},
- "node_modules/es-define-property": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
- "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
- "node_modules/es-object-atoms": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
- "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-set-tostringtag": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
- "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.6",
- "has-tostringtag": "^1.0.2",
- "hasown": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
"node_modules/esbuild": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
@@ -2688,239 +1657,6 @@
"node": ">=6"
}
},
- "node_modules/escape-string-regexp": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
- "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/eslint": {
- "version": "8.57.1",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
- "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
- "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.2.0",
- "@eslint-community/regexpp": "^4.6.1",
- "@eslint/eslintrc": "^2.1.4",
- "@eslint/js": "8.57.1",
- "@humanwhocodes/config-array": "^0.13.0",
- "@humanwhocodes/module-importer": "^1.0.1",
- "@nodelib/fs.walk": "^1.2.8",
- "@ungap/structured-clone": "^1.2.0",
- "ajv": "^6.12.4",
- "chalk": "^4.0.0",
- "cross-spawn": "^7.0.2",
- "debug": "^4.3.2",
- "doctrine": "^3.0.0",
- "escape-string-regexp": "^4.0.0",
- "eslint-scope": "^7.2.2",
- "eslint-visitor-keys": "^3.4.3",
- "espree": "^9.6.1",
- "esquery": "^1.4.2",
- "esutils": "^2.0.2",
- "fast-deep-equal": "^3.1.3",
- "file-entry-cache": "^6.0.1",
- "find-up": "^5.0.0",
- "glob-parent": "^6.0.2",
- "globals": "^13.19.0",
- "graphemer": "^1.4.0",
- "ignore": "^5.2.0",
- "imurmurhash": "^0.1.4",
- "is-glob": "^4.0.0",
- "is-path-inside": "^3.0.3",
- "js-yaml": "^4.1.0",
- "json-stable-stringify-without-jsonify": "^1.0.1",
- "levn": "^0.4.1",
- "lodash.merge": "^4.6.2",
- "minimatch": "^3.1.2",
- "natural-compare": "^1.4.0",
- "optionator": "^0.9.3",
- "strip-ansi": "^6.0.1",
- "text-table": "^0.2.0"
- },
- "bin": {
- "eslint": "bin/eslint.js"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/eslint-plugin-react-hooks": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz",
- "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0"
- }
- },
- "node_modules/eslint-plugin-react-refresh": {
- "version": "0.4.26",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz",
- "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "eslint": ">=8.40"
- }
- },
- "node_modules/eslint-scope": {
- "version": "7.2.2",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
- "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "esrecurse": "^4.3.0",
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/eslint-visitor-keys": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
- "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/eslint/node_modules/brace-expansion": {
- "version": "1.1.14",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
- "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/eslint/node_modules/minimatch": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
- "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/espree": {
- "version": "9.6.1",
- "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
- "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "acorn": "^8.9.0",
- "acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^3.4.1"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/esquery": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
- "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "estraverse": "^5.1.0"
- },
- "engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/esrecurse": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
- "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/estraverse": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
- "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/esutils": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
- "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/eventemitter3": {
- "version": "4.0.7",
- "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
- "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
- "license": "MIT"
- },
- "node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-equals": {
- "version": "5.4.0",
- "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz",
- "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==",
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
"node_modules/fast-glob": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
@@ -2951,444 +1687,102 @@
"node": ">= 6"
}
},
- "node_modules/fast-json-stable-stringify": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
- "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-levenshtein": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
- "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/fastq": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
"integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
"dev": true,
- "license": "ISC",
- "dependencies": {
- "reusify": "^1.0.4"
- }
- },
- "node_modules/file-entry-cache": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
- "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "flat-cache": "^3.0.4"
- },
- "engines": {
- "node": "^10.12.0 || >=12.0.0"
- }
- },
- "node_modules/fill-range": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
- "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "to-regex-range": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/find-up": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
- "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "locate-path": "^6.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/flat-cache": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
- "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "flatted": "^3.2.9",
- "keyv": "^4.5.3",
- "rimraf": "^3.0.2"
- },
- "engines": {
- "node": "^10.12.0 || >=12.0.0"
- }
- },
- "node_modules/flatted": {
- "version": "3.4.2",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
- "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/follow-redirects": {
- "version": "1.16.0",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
- "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
- "funding": [
- {
- "type": "individual",
- "url": "https://github.com/sponsors/RubenVerborgh"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=4.0"
- },
- "peerDependenciesMeta": {
- "debug": {
- "optional": true
- }
- }
- },
- "node_modules/form-data": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
- "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
- "license": "MIT",
- "dependencies": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "es-set-tostringtag": "^2.1.0",
- "hasown": "^2.0.2",
- "mime-types": "^2.1.12"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/fraction.js": {
- "version": "5.3.4",
- "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
- "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "*"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/rawify"
- }
- },
- "node_modules/fs.realpath": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
- "license": "ISC"
- },
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/function-bind": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/gensync": {
- "version": "1.0.0-beta.2",
- "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
- "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/get-intrinsic": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
- "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "es-define-property": "^1.0.1",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.1.1",
- "function-bind": "^1.1.2",
- "get-proto": "^1.0.1",
- "gopd": "^1.2.0",
- "has-symbols": "^1.1.0",
- "hasown": "^2.0.2",
- "math-intrinsics": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/get-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
- "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
- "license": "MIT",
- "dependencies": {
- "dunder-proto": "^1.0.1",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/glob": {
- "version": "7.2.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
- "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
- "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
- "license": "ISC",
- "dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.1.1",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- },
- "engines": {
- "node": "*"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/glob-parent": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
- "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.3"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/glob/node_modules/brace-expansion": {
- "version": "1.1.14",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
- "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/glob/node_modules/minimatch": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
- "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/globals": {
- "version": "13.24.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
- "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "type-fest": "^0.20.2"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/globby": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
- "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "array-union": "^2.1.0",
- "dir-glob": "^3.0.1",
- "fast-glob": "^3.2.9",
- "ignore": "^5.2.0",
- "merge2": "^1.4.1",
- "slash": "^3.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/gopd": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
- "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/graphemer": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
- "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/has-symbols": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
- "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
}
},
- "node_modules/has-tostringtag": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
- "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "has-symbols": "^1.0.3"
+ "to-regex-range": "^5.0.1"
},
"engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "node": ">=8"
}
},
- "node_modules/hasown": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
- "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
+ "node_modules/fraction.js": {
+ "version": "5.3.4",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
+ "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "function-bind": "^1.1.2"
- },
"engines": {
- "node": ">= 0.4"
+ "node": "*"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/rawify"
}
},
- "node_modules/ignore": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
- "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
+ "hasInstallScript": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
"engines": {
- "node": ">= 4"
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
- "node_modules/import-fresh": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
- "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "parent-module": "^1.0.0",
- "resolve-from": "^4.0.0"
- },
- "engines": {
- "node": ">=6"
- },
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/imurmurhash": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=0.8.19"
+ "node": ">=6.9.0"
}
},
- "node_modules/inflight": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
- "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
"license": "ISC",
"dependencies": {
- "once": "^1.3.0",
- "wrappy": "1"
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
}
},
- "node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "license": "ISC"
- },
- "node_modules/internmap": {
+ "node_modules/hasown": {
"version": "2.0.3",
- "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
- "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
- "license": "ISC",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
+ "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
"engines": {
- "node": ">=12"
+ "node": ">= 0.4"
}
},
"node_modules/is-binary-path": {
@@ -3453,23 +1847,6 @@
"node": ">=0.12.0"
}
},
- "node_modules/is-path-inside": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
- "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/jiti": {
"version": "1.21.7",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
@@ -3480,31 +1857,12 @@
"jiti": "bin/jiti.js"
}
},
- "node_modules/js-sha3": {
- "version": "0.8.0",
- "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
- "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==",
- "license": "MIT"
- },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"license": "MIT"
},
- "node_modules/js-yaml": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
- "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "argparse": "^2.0.1"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
"node_modules/jsesc": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
@@ -3518,27 +1876,6 @@
"node": ">=6"
}
},
- "node_modules/json-buffer": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
- "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/json-schema-traverse": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/json-stable-stringify-without-jsonify": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
- "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
@@ -3552,36 +1889,6 @@
"node": ">=6"
}
},
- "node_modules/keyv": {
- "version": "4.5.4",
- "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
- "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "json-buffer": "3.0.1"
- }
- },
- "node_modules/leaflet": {
- "version": "1.9.4",
- "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
- "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
- "license": "BSD-2-Clause"
- },
- "node_modules/levn": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
- "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "prelude-ls": "^1.2.1",
- "type-check": "~0.4.0"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
"node_modules/lilconfig": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
@@ -3602,35 +1909,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/locate-path": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
- "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-locate": "^5.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/lodash": {
- "version": "4.18.1",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
- "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
- "license": "MIT"
- },
- "node_modules/lodash.merge": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
- "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -3653,25 +1931,6 @@
"yallist": "^3.0.2"
}
},
- "node_modules/match-sorter": {
- "version": "6.3.4",
- "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.4.tgz",
- "integrity": "sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.23.8",
- "remove-accents": "0.5.0"
- }
- },
- "node_modules/math-intrinsics": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
- "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -3696,49 +1955,6 @@
"node": ">=8.6"
}
},
- "node_modules/microseconds": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz",
- "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==",
- "license": "MIT"
- },
- "node_modules/mime-db": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime-types": {
- "version": "2.1.35",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "license": "MIT",
- "dependencies": {
- "mime-db": "1.52.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/minimatch": {
- "version": "9.0.3",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
- "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -3758,15 +1974,6 @@
"thenify-all": "^1.0.0"
}
},
- "node_modules/nano-time": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz",
- "integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==",
- "license": "ISC",
- "dependencies": {
- "big-integer": "^1.6.16"
- }
- },
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
@@ -3786,13 +1993,6 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
- "node_modules/natural-compare": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
- "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/node-releases": {
"version": "2.0.38",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz",
@@ -3814,6 +2014,7 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -3829,129 +2030,12 @@
"node": ">= 6"
}
},
- "node_modules/oblivious-set": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz",
- "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==",
- "license": "MIT"
- },
- "node_modules/once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "license": "ISC",
- "dependencies": {
- "wrappy": "1"
- }
- },
- "node_modules/optionator": {
- "version": "0.9.4",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
- "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "deep-is": "^0.1.3",
- "fast-levenshtein": "^2.0.6",
- "levn": "^0.4.1",
- "prelude-ls": "^1.2.1",
- "type-check": "^0.4.0",
- "word-wrap": "^1.2.5"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/p-limit": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
- "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "yocto-queue": "^0.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/p-locate": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
- "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^3.0.2"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/parent-module": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
- "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "callsites": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/path-exists": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
- "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-is-absolute": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/path-key": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
- "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true,
- "license": "MIT"
- },
- "node_modules/path-type": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
- "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
+ "license": "MIT"
},
"node_modules/picocolors": {
"version": "1.1.1",
@@ -3994,9 +2078,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.10",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz",
- "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==",
+ "version": "8.5.12",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz",
+ "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==",
"dev": true,
"funding": [
{
@@ -4156,52 +2240,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/prelude-ls": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
- "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/prop-types": {
- "version": "15.8.1",
- "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
- "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.4.0",
- "object-assign": "^4.1.1",
- "react-is": "^16.13.1"
- }
- },
- "node_modules/prop-types/node_modules/react-is": {
- "version": "16.13.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "license": "MIT"
- },
- "node_modules/proxy-from-env": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
- "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
- "license": "MIT",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/punycode": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
- "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -4248,52 +2286,6 @@
"react": "^18.3.1"
}
},
- "node_modules/react-is": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
- "license": "MIT"
- },
- "node_modules/react-leaflet": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.1.tgz",
- "integrity": "sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==",
- "license": "Hippocratic-2.1",
- "dependencies": {
- "@react-leaflet/core": "^2.1.0"
- },
- "peerDependencies": {
- "leaflet": "^1.9.0",
- "react": "^18.0.0",
- "react-dom": "^18.0.0"
- }
- },
- "node_modules/react-query": {
- "version": "3.39.3",
- "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz",
- "integrity": "sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.5.5",
- "broadcast-channel": "^3.4.1",
- "match-sorter": "^6.0.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/tannerlinsley"
- },
- "peerDependencies": {
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
- },
- "peerDependenciesMeta": {
- "react-dom": {
- "optional": true
- },
- "react-native": {
- "optional": true
- }
- }
- },
"node_modules/react-refresh": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
@@ -4304,69 +2296,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/react-router": {
- "version": "6.30.3",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz",
- "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==",
- "license": "MIT",
- "dependencies": {
- "@remix-run/router": "1.23.2"
- },
- "engines": {
- "node": ">=14.0.0"
- },
- "peerDependencies": {
- "react": ">=16.8"
- }
- },
- "node_modules/react-router-dom": {
- "version": "6.30.3",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz",
- "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==",
- "license": "MIT",
- "dependencies": {
- "@remix-run/router": "1.23.2",
- "react-router": "6.30.3"
- },
- "engines": {
- "node": ">=14.0.0"
- },
- "peerDependencies": {
- "react": ">=16.8",
- "react-dom": ">=16.8"
- }
- },
- "node_modules/react-smooth": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
- "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==",
- "license": "MIT",
- "dependencies": {
- "fast-equals": "^5.0.1",
- "prop-types": "^15.8.1",
- "react-transition-group": "^4.4.5"
- },
- "peerDependencies": {
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
- "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
- }
- },
- "node_modules/react-transition-group": {
- "version": "4.4.5",
- "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
- "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "@babel/runtime": "^7.5.5",
- "dom-helpers": "^5.0.1",
- "loose-envify": "^1.4.0",
- "prop-types": "^15.6.2"
- },
- "peerDependencies": {
- "react": ">=16.6.0",
- "react-dom": ">=16.6.0"
- }
- },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -4390,44 +2319,6 @@
"node": ">=8.10.0"
}
},
- "node_modules/recharts": {
- "version": "2.15.4",
- "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz",
- "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==",
- "license": "MIT",
- "dependencies": {
- "clsx": "^2.0.0",
- "eventemitter3": "^4.0.1",
- "lodash": "^4.17.21",
- "react-is": "^18.3.1",
- "react-smooth": "^4.0.4",
- "recharts-scale": "^0.4.4",
- "tiny-invariant": "^1.3.1",
- "victory-vendor": "^36.6.8"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
- "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
- }
- },
- "node_modules/recharts-scale": {
- "version": "0.4.5",
- "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
- "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
- "license": "MIT",
- "dependencies": {
- "decimal.js-light": "^2.4.1"
- }
- },
- "node_modules/remove-accents": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz",
- "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==",
- "license": "MIT"
- },
"node_modules/resolve": {
"version": "1.22.12",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
@@ -4450,16 +2341,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/resolve-from": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
- "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/reusify": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
@@ -4471,22 +2352,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/rimraf": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
- "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
- "deprecated": "Rimraf versions prior to v4 are no longer supported",
- "license": "ISC",
- "dependencies": {
- "glob": "^7.1.3"
- },
- "bin": {
- "rimraf": "bin.js"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/rollup": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz",
@@ -4566,49 +2431,13 @@
}
},
"node_modules/semver": {
- "version": "7.7.4",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
- "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/shebang-command": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
- "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "shebang-regex": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/shebang-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
- "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/slash": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
- "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
}
},
"node_modules/source-map-js": {
@@ -4621,32 +2450,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-json-comments": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
- "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/sucrase": {
"version": "3.35.1",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
@@ -4670,19 +2473,6 @@
"node": ">=16 || 14 >=14.17"
}
},
- "node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
@@ -4734,13 +2524,6 @@
"node": ">=14.0.0"
}
},
- "node_modules/text-table": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
- "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/thenify": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
@@ -4764,12 +2547,6 @@
"node": ">=0.8"
}
},
- "node_modules/tiny-invariant": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
- "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
- "license": "MIT"
- },
"node_modules/tinyglobby": {
"version": "0.2.16",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
@@ -4831,19 +2608,6 @@
"node": ">=8.0"
}
},
- "node_modules/ts-api-utils": {
- "version": "1.4.3",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
- "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=16"
- },
- "peerDependencies": {
- "typescript": ">=4.2.0"
- }
- },
"node_modules/ts-interface-checker": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
@@ -4851,32 +2615,6 @@
"dev": true,
"license": "Apache-2.0"
},
- "node_modules/type-check": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
- "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "prelude-ls": "^1.2.1"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/type-fest": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
- "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
- "dev": true,
- "license": "(MIT OR CC0-1.0)",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
@@ -4891,16 +2629,6 @@
"node": ">=14.17"
}
},
- "node_modules/unload": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz",
- "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@babel/runtime": "^7.6.2",
- "detect-node": "^2.0.4"
- }
- },
"node_modules/update-browserslist-db": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
@@ -4932,25 +2660,6 @@
"browserslist": ">= 4.21.0"
}
},
- "node_modules/uri-js": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
- "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "punycode": "^2.1.0"
- }
- },
- "node_modules/use-sync-external-store": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
- "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
- "license": "MIT",
- "peerDependencies": {
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
- }
- },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -4958,28 +2667,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/victory-vendor": {
- "version": "36.9.2",
- "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
- "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==",
- "license": "MIT AND ISC",
- "dependencies": {
- "@types/d3-array": "^3.0.3",
- "@types/d3-ease": "^3.0.0",
- "@types/d3-interpolate": "^3.0.1",
- "@types/d3-scale": "^4.0.2",
- "@types/d3-shape": "^3.1.0",
- "@types/d3-time": "^3.0.0",
- "@types/d3-timer": "^3.0.0",
- "d3-array": "^3.1.6",
- "d3-ease": "^3.0.1",
- "d3-interpolate": "^3.0.1",
- "d3-scale": "^4.0.2",
- "d3-shape": "^3.1.0",
- "d3-time": "^3.0.0",
- "d3-timer": "^3.0.1"
- }
- },
"node_modules/vite": {
"version": "5.4.21",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
@@ -5040,85 +2727,12 @@
}
}
},
- "node_modules/which": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
- "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "isexe": "^2.0.0"
- },
- "bin": {
- "node-which": "bin/node-which"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/word-wrap": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
- "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
- "license": "ISC"
- },
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true,
"license": "ISC"
- },
- "node_modules/yocto-queue": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
- "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/zustand": {
- "version": "4.5.7",
- "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
- "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
- "license": "MIT",
- "dependencies": {
- "use-sync-external-store": "^1.2.2"
- },
- "engines": {
- "node": ">=12.7.0"
- },
- "peerDependencies": {
- "@types/react": ">=16.8",
- "immer": ">=9.0.6",
- "react": ">=16.8"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- },
- "immer": {
- "optional": true
- },
- "react": {
- "optional": true
- }
- }
}
}
}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..0c0ac9e5ae039532f8cbcf5a4bbd55a29e323ef4
--- /dev/null
+++ b/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "communityone-org-card",
+ "version": "1.0.0",
+ "description": "CommunityOne Organization Card",
+ "private": true,
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "@headlessui/react": "^1.7.17",
+ "@heroicons/react": "^2.0.18"
+ },
+ "devDependencies": {
+ "@types/react": "^18.2.43",
+ "@types/react-dom": "^18.2.17",
+ "@vitejs/plugin-react": "^4.2.1",
+ "autoprefixer": "^10.4.16",
+ "postcss": "^8.4.32",
+ "tailwindcss": "^3.3.6",
+ "typescript": "^5.2.2",
+ "vite": "^5.0.8"
+ }
+}
diff --git a/frontend/postcss.config.js b/postcss.config.js
similarity index 76%
rename from frontend/postcss.config.js
rename to postcss.config.js
index 33ad091d26d8a9dc95ebdf616e217d985ec215b8..2e7af2b7f1a6f391da1631d93968a9d487ba977d 100644
--- a/frontend/postcss.config.js
+++ b/postcss.config.js
@@ -1,4 +1,4 @@
-module.exports = {
+export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
diff --git a/src/App.tsx b/src/App.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e461a1bf09919756093280f01b4173e4698d9467
--- /dev/null
+++ b/src/App.tsx
@@ -0,0 +1,362 @@
+import { useState, useEffect } from 'react'
+import {
+ RocketLaunchIcon,
+ ChartBarIcon,
+ BuildingLibraryIcon,
+ GlobeAltIcon,
+ ArrowRightIcon,
+ CheckCircleIcon,
+ DocumentTextIcon,
+ CodeBracketIcon,
+ EnvelopeIcon,
+ Bars3Icon,
+ XMarkIcon
+} from '@heroicons/react/24/outline'
+
+const datasets = [
+ { name: 'Nonprofit Organizations', records: '1.95M', url: 'https://huggingface.co/datasets/CommunityOne/one-nonprofits-organizations', icon: BuildingLibraryIcon },
+ { name: 'Nonprofit Financials', records: '1.95M', url: 'https://huggingface.co/datasets/CommunityOne/one-nonprofits-financials', icon: ChartBarIcon },
+ { name: 'Nonprofit Programs', records: '1.95M', url: 'https://huggingface.co/datasets/CommunityOne/one-nonprofits-programs', icon: DocumentTextIcon },
+ { name: 'Nonprofit Locations', records: '1.95M', url: 'https://huggingface.co/datasets/CommunityOne/one-nonprofits-locations', icon: GlobeAltIcon },
+ { name: 'Public Meetings Calendar', records: '153K', url: 'https://huggingface.co/datasets/CommunityOne/one-meetings-calendar', icon: RocketLaunchIcon }
+]
+
+const features = [
+ { title: '90,000+ Jurisdictions', description: 'Cities, counties, and state governments' },
+ { title: '1.95M Nonprofits', description: 'Organizations with complete data' },
+ { title: '153K+ Public Meetings', description: 'Agendas and transcripts' },
+ { title: 'Open Source & Free', description: 'MIT License, accessible to all' }
+]
+
+function App() {
+ const [activeSection, setActiveSection] = useState('home')
+ const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
+
+ // Smooth scroll to section
+ const scrollToSection = (sectionId: string) => {
+ setActiveSection(sectionId)
+ setMobileMenuOpen(false)
+
+ const element = document.getElementById(sectionId)
+ if (element) {
+ const offset = 80 // Account for sticky header
+ const elementPosition = element.getBoundingClientRect().top
+ const offsetPosition = elementPosition + window.pageYOffset - offset
+
+ window.scrollTo({
+ top: offsetPosition,
+ behavior: 'smooth'
+ })
+ }
+ }
+
+ // Track active section on scroll
+ useEffect(() => {
+ const handleScroll = () => {
+ const sections = ['home', 'datasets', 'about']
+ const scrollPosition = window.scrollY + 150
+
+ for (const section of sections) {
+ const element = document.getElementById(section)
+ if (element) {
+ const { offsetTop, offsetHeight } = element
+ if (scrollPosition >= offsetTop && scrollPosition < offsetTop + offsetHeight) {
+ setActiveSection(section)
+ break
+ }
+ }
+ }
+ }
+
+ window.addEventListener('scroll', handleScroll)
+ return () => window.removeEventListener('scroll', handleScroll)
+ }, [])
+
+ return (
+
+ {/* Navigation */}
+
+
+
+ {/* Logo */}
+
+
+
+
CommunityOne
+
The open path to everything local
+
+
+
+ {/* Desktop Navigation - Centered */}
+
+ scrollToSection('home')}
+ className={`text-sm font-medium transition-all pb-1 ${
+ activeSection === 'home'
+ ? 'text-primary-600 border-b-2 border-primary-600'
+ : 'text-gray-600 hover:text-gray-900'
+ }`}
+ >
+ Home
+
+ scrollToSection('datasets')}
+ className={`text-sm font-medium transition-all pb-1 ${
+ activeSection === 'datasets'
+ ? 'text-primary-600 border-b-2 border-primary-600'
+ : 'text-gray-600 hover:text-gray-900'
+ }`}
+ >
+ Datasets
+
+ scrollToSection('about')}
+ className={`text-sm font-medium transition-all pb-1 ${
+ activeSection === 'about'
+ ? 'text-primary-600 border-b-2 border-primary-600'
+ : 'text-gray-600 hover:text-gray-900'
+ }`}
+ >
+ About
+
+
+
+ {/* Desktop CTA */}
+
+ Launch App
+
+
+
+ {/* Mobile Menu Button */}
+
setMobileMenuOpen(!mobileMenuOpen)}
+ className="md:hidden p-2 rounded-lg text-gray-600 hover:bg-gray-100 transition-colors"
+ >
+ {mobileMenuOpen ? (
+
+ ) : (
+
+ )}
+
+
+
+ {/* Mobile Menu */}
+ {mobileMenuOpen && (
+
+
+
scrollToSection('home')}
+ className={`text-left px-4 py-2 text-sm font-medium rounded-lg transition-colors ${
+ activeSection === 'home' ? 'bg-primary-50 text-primary-600' : 'text-gray-600 hover:bg-gray-50'
+ }`}
+ >
+ Home
+
+
scrollToSection('datasets')}
+ className={`text-left px-4 py-2 text-sm font-medium rounded-lg transition-colors ${
+ activeSection === 'datasets' ? 'bg-primary-50 text-primary-600' : 'text-gray-600 hover:bg-gray-50'
+ }`}
+ >
+ Datasets
+
+
scrollToSection('about')}
+ className={`text-left px-4 py-2 text-sm font-medium rounded-lg transition-colors ${
+ activeSection === 'about' ? 'bg-primary-50 text-primary-600' : 'text-gray-600 hover:bg-gray-50'
+ }`}
+ >
+ About
+
+
+ Launch App
+
+
+
+ )}
+
+
+
+ {/* Hero Section */}
+
+
+
+
+
+ Open Source • 1.95M Nonprofits • MIT License
+
+
+ Making Civic Data
+ Accessible
+
+
+ Comprehensive platform for discovering, analyzing, and tracking local government activities,
+ nonprofit organizations, and civic engagement opportunities across the United States.
+
+
+
+
+ Explore Platform
+
+
scrollToSection('datasets')}
+ className="inline-flex items-center px-8 py-4 bg-white text-gray-700 text-lg font-medium rounded-xl hover:bg-gray-50 transition-all border-2 border-gray-200"
+ >
+
+ Browse Datasets
+
+
+
+
+ {/* Features Grid */}
+
+ {features.map((feature, idx) => (
+
+
{feature.title}
+
{feature.description}
+
+ ))}
+
+
+
+
+ {/* Datasets Section */}
+
+
+
Open Datasets
+
High-quality, regularly-updated civic data for research and analysis
+
+
+
+
+
+ {/* About Section */}
+
+
+
About CommunityOne
+
+
+ CommunityOne provides open-source tools and data for civic engagement, local government
+ transparency, and nonprofit discovery across the United States.
+
+
Mission
+
+ Making civic data accessible, usable, and actionable for:
+
+
+
+
+ Policy makers and advocates
+
+
+
+ Researchers and journalists
+
+
+
+ Nonprofit organizations
+
+
+
+ Engaged citizens
+
+
+
Contact
+
+
+
+ Powered by open data from IRS, Census Bureau, local governments, and civic tech partners.
+
+
+
+
+
+
+ {/* Footer */}
+
+
+
+
+
+
+ CommunityOne
+
+
The open path to everything local
+
+
+
+
+
+
© 2026 CommunityOne. Open source under MIT License.
+
+
+
+
+ )
+}
+
+export default App
diff --git a/src/index.css b/src/index.css
new file mode 100644
index 0000000000000000000000000000000000000000..84c47b97582e1a6fbf1cbca8e6b5342c0af96f69
--- /dev/null
+++ b/src/index.css
@@ -0,0 +1,31 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+:root {
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ font-weight: 400;
+}
+
+body {
+ margin: 0;
+ min-height: 100vh;
+}
+
+/* Scroll animation */
+@keyframes slideUp {
+ from {
+ opacity: 0;
+ transform: translateY(30px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+/* Smooth scrolling */
+html {
+ scroll-behavior: smooth;
+}
diff --git a/src/main.tsx b/src/main.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..3d7150da80e43e3650342aa4758fa8b74e95d6d6
--- /dev/null
+++ b/src/main.tsx
@@ -0,0 +1,10 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import App from './App.tsx'
+import './index.css'
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+ ,
+)
diff --git a/tailwind.config.js b/tailwind.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..d9ef2d785a00b3b4a83843986e87c37b2953925d
--- /dev/null
+++ b/tailwind.config.js
@@ -0,0 +1,26 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: [
+ "./index.html",
+ "./src/**/*.{js,ts,jsx,tsx}",
+ ],
+ theme: {
+ extend: {
+ colors: {
+ primary: {
+ 50: '#eff6ff',
+ 100: '#dbeafe',
+ 200: '#bfdbfe',
+ 300: '#93c5fd',
+ 400: '#60a5fa',
+ 500: '#3b82f6',
+ 600: '#2563eb',
+ 700: '#1d4ed8',
+ 800: '#1e40af',
+ 900: '#1e3a8a',
+ },
+ },
+ },
+ },
+ plugins: [],
+}
diff --git a/frontend/tsconfig.json b/tsconfig.json
similarity index 71%
rename from frontend/tsconfig.json
rename to tsconfig.json
index 415b301c006daf9136c279bd68530d7b8ebe0ced..3934b8f6d676d599058af5529521ad710c03121d 100644
--- a/frontend/tsconfig.json
+++ b/tsconfig.json
@@ -5,27 +5,16 @@
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
-
- /* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
-
- /* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
- "noFallthroughCasesInSwitch": true,
-
- /* Path mapping */
- "ignoreDeprecations": "5.0",
- "baseUrl": ".",
- "paths": {
- "@/*": ["./src/*"]
- }
+ "noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
diff --git a/frontend/tsconfig.node.json b/tsconfig.node.json
similarity index 100%
rename from frontend/tsconfig.node.json
rename to tsconfig.node.json
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e3d3813bf7554abf54590b170dd5f5041a6aadb8
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,14 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ port: 7860,
+ host: '0.0.0.0'
+ },
+ build: {
+ outDir: 'dist',
+ emptyOutDir: true
+ }
+})