# Project export: CareMap

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2025
- Tagline: CareMap is an AI-powered tool that helps people in crisis instantly find local services like shelters, food banks, and public restrooms, catered especially to San Francisco.
- Devpost: https://devpost.com/software/caremap-yro2td
- GitHub: https://github.com/AishaErel/final_hack_cal
- Team: 2 GitHub contributor(s) — Aisha (3 commits), Michael Yen (1 commits)

## Devpost submission (written by the team)

### Inspiration

In San Francisco, finding basic help, like a place to sleep, get a meal, or use a clean restroom, is often way harder than it should be. While resources exist, they’re scattered across websites, PDFs, and phone lines, making it confusing or impossible to find help quickly. More than 8,300 people are unhoused on any given night, and over 20,000 seek homeless services each year. But still, more than half sleep outside. Food insecurity is also high, 117,000 residents struggle to get enough to eat. And though the city provides public restrooms and water fountains, they're not always easy to locate. In fact, one restroom program logged 750,000 visits in just 9 months, showing how urgent the need is. That’s why we built CareMap: a tool that brings together real local services like shelters, food banks, and restrooms. You just enter your ZIP code and preferences and get a clean, AI-powered summary of what’s nearby and open now. Because in a moment of crisis, no one should be digging through a dozen websites.

### What it does

CareMap helps users: 📍 Find local help fast by entering a ZIP code and choosing preferences (like ID required, walk-ins welcome, multilingual staff, etc.) 🧠 Get an AI-generated summary (via Claude 3) explaining the best matches 🗺️ View results visually on a live interactive map 💬 Chat with an AI to ask questions like “Where can I get food tonight?” We prioritize accessibility, urgency, and clarity to serve those who need help the most.

### How we built it

Frontend React + TypeScript TailwindCSS for UI, Lucide for icons Leaflet.js for mapping Backend Node.js and FastAPI hybrid Claude API (Anthropic) to summarize filtered service results Service dataset: shelters, public restrooms, water fountains, food programs — all manually cleaned and annotated Custom preference system: flags for requires_id, lgbtq_friendly, multilingual_staff, and more

### Challenges we ran into

Mapping inconsistent real-world data (bad ZIPs, missing lat/lng) Managing geolocation and ZIP code fallbacks in the UI Syncing the hybrid backend setup between Node.js and FastAPI Making the map work smoothly with filtering and user location

### Accomplishments we're proud of

🧠 Deployed Claude 3 to generate natural, helpful responses 🗺️ Built a fully working interactive map with real SF data 📊 Created a clean CSV dataset from messy public sources Made the entire experience inclusive, intuitive, and fast

### What we learned

How to clean and normalize geospatial datasets How to use Claude effectively for real-time summarization How to build voice-powered apps with Vapi How to design for accessibility and urgency, not just aesthetics That even small filters like “walk-ins welcome” can make a huge impact

### What's next

Live data integration: Sync with SF’s city APIs for real-time hours and service status Expand to more cities: Starting with Oakland and LA Mobile-first UX: Focused design for public kiosk or shelter use Multilingual summaries and voice support Partner with local orgs to keep service info verified and current

## README (from the GitHub repository)

**Inspiration** In San Francisco, finding basic help, like a place to sleep, get a meal, or use a clean restroom, is often way harder than it should be. While resources exist, they’re scattered across websites, PDFs, and phone lines, making it confusing or impossible to find help quickly. More than 8,300 people are unhoused on any given night, and over 20,000 seek homeless services each year. But still, more than half sleep outside. Food insecurity is also high, 117,000 residents struggle to get enough to eat. And though the city provides public restrooms and water fountains, they're not always easy to locate. In fact, one restroom program logged 750,000 visits in just 9 months, showing how urgent the need is. That’s why we built CareMap: a tool that brings together real local services like shelters, food banks, and restrooms. You just enter your ZIP code and preferences and get a clean, AI-powered summary of what’s nearby and open now. Because in a moment of crisis, no one should be digging through a dozen websites.

**What it does** CareMap helps users: 📍 Find local help fast by entering a ZIP code and choosing preferences (like ID required, walk-ins welcome, multilingual staff, etc.) 🧠 Get an AI-generated summary (via Claude 3) explaining the best matches 🗺️ View results visually on a live interactive map 💬 Chat with an AI to ask questions like “Where can I get food tonight?” We prioritize accessibility, urgency, and clarity to serve those who need help the most.

**How we built it** Frontend React + TypeScript TailwindCSS for UI, Lucide for icons Leaflet.js for mapping Backend Node.js and FastAPI hybrid Claude API (Anthropic) to summarize filtered service results Service dataset: shelters, public restrooms, water fountains, food programs — all manually cleaned and annotated Custom preference system: flags for requires_id, lgbtq_friendly, multilingual_staff, and more

**Challenges we ran into** Mapping inconsistent real-world data (bad ZIPs, missing lat/lng) Managing geolocation and ZIP code fallbacks in the UI Syncing the hybrid backend setup between Node.js and FastAPI Making the map work smoothly with filtering and user location

**Accomplishments that we're proud of 🧠** Deployed Claude 3 to generate natural, helpful responses 🗺️ Built a fully working interactive map with real SF data 📊 Created a clean CSV dataset from messy public sources Made the entire experience inclusive, intuitive, and fast

**What we learned** How to clean and normalize geospatial datasets How to use Claude effectively for real-time summarization How to build voice-powered apps with Vapi How to design for accessibility and urgency, not just aesthetics That even small filters like “walk-ins welcome” can make a huge impact
**
**What's next for CareMap Live data integration:**** Sync with SF’s city APIs for real-time hours and service status Expand to more cities: Starting with Oakland and LA Mobile-first UX: Focused design for public kiosk or shelter use Multilingual summaries and voice support Partner with local orgs to keep service info verified and current


## Detected evidence (automated analysis)

Indexed codebase: 72 recognized source files, 209 KB.
- CSS (language) — detected in the code
- Express (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- FastAPI (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (85 of 85)

```
.gitignore
bun.lockb
caremap-navigator-backend/final_all_with_preferences.csv
caremap-navigator-backend/final_all.csv
caremap-navigator-backend/main.py
caremap-navigator-backend/package.json
caremap-navigator-backend/sample.csv
caremap-navigator-backend/server.js
components.json
eslint.config.js
index.html
package.json
postcss.config.js
public/robots.txt
README.md
src/App.css
src/App.tsx
src/c
src/components/backend/tester.js
src/components/HomePage.tsx
src/components/MainApp.tsx
src/components/PreferencesSetup.tsx
src/components/ui/accordion.tsx
src/components/ui/alert-dialog.tsx
src/components/ui/alert.tsx
src/components/ui/aspect-ratio.tsx
src/components/ui/avatar.tsx
src/components/ui/badge.tsx
src/components/ui/breadcrumb.tsx
src/components/ui/button.tsx
src/components/ui/calendar.tsx
src/components/ui/card.tsx
src/components/ui/carousel.tsx
src/components/ui/chart.tsx
src/components/ui/checkbox.tsx
src/components/ui/collapsible.tsx
src/components/ui/command.tsx
src/components/ui/context-menu.tsx
src/components/ui/dialog.tsx
src/components/ui/drawer.tsx
src/components/ui/dropdown-menu.tsx
src/components/ui/form.tsx
src/components/ui/hover-card.tsx
src/components/ui/input-otp.tsx
src/components/ui/input.tsx
src/components/ui/label.tsx
src/components/ui/menubar.tsx
src/components/ui/navigation-menu.tsx
src/components/ui/pagination.tsx
src/components/ui/popover.tsx
src/components/ui/progress.tsx
src/components/ui/radio-group.tsx
src/components/ui/resizable.tsx
src/components/ui/scroll-area.tsx
src/components/ui/select.tsx
src/components/ui/separator.tsx
src/components/ui/sheet.tsx
src/components/ui/sidebar.tsx
src/components/ui/skeleton.tsx
src/components/ui/slider.tsx
src/components/ui/sonner.tsx
src/components/ui/switch.tsx
src/components/ui/table.tsx
src/components/ui/tabs.tsx
src/components/ui/textarea.tsx
src/components/ui/toast.tsx
src/components/ui/toaster.tsx
src/components/ui/toggle-group.tsx
src/components/ui/toggle.tsx
src/components/ui/tooltip.tsx
src/components/ui/use-toast.ts
src/constants/Constants.js
src/hooks/use-mobile.tsx
src/hooks/use-toast.ts
src/index.css
src/lib/utils.ts
src/main.tsx
src/pages/Index.tsx
src/pages/NotFound.tsx
src/vite-env.d.ts
tailwind.config.ts
tsconfig.app.json
tsconfig.json
tsconfig.node.json
vite.config.ts
```

### Dependencies

- caremap-navigator-backend/package.json: cors@^2.8.5, csv-parser@^3.2.0, express@^5.1.0, fs@^0.0.1-security
- package.json: @eslint/js@^9.9.0, @hookform/resolvers@^3.9.0, @radix-ui/react-accordion@^1.2.0, @radix-ui/react-alert-dialog@^1.1.1, @radix-ui/react-aspect-ratio@^1.1.0, @radix-ui/react-avatar@^1.1.0, @radix-ui/react-checkbox@^1.1.1, @radix-ui/react-collapsible@^1.1.0, @radix-ui/react-context-menu@^2.2.1, @radix-ui/react-dialog@^1.1.2, @radix-ui/react-dropdown-menu@^2.1.1, @radix-ui/react-hover-card@^1.1.1, @radix-ui/react-label@^2.1.0, @radix-ui/react-menubar@^1.1.1, @radix-ui/react-navigation-menu@^1.2.0, @radix-ui/react-popover@^1.1.1, @radix-ui/react-progress@^1.1.0, @radix-ui/react-radio-group@^1.2.0, @radix-ui/react-scroll-area@^1.1.0, @radix-ui/react-select@^2.1.1, @radix-ui/react-separator@^1.1.0, @radix-ui/react-slider@^1.2.0, @radix-ui/react-slot@^1.1.0, @radix-ui/react-switch@^1.1.0, @radix-ui/react-tabs@^1.1.0, @radix-ui/react-toast@^1.2.1, @radix-ui/react-toggle@^1.1.0, @radix-ui/react-toggle-group@^1.1.0, @radix-ui/react-tooltip@^1.1.4, @tailwindcss/typography@^0.5.15, @tanstack/react-query@^5.56.2, @types/node@^22.5.5, @types/react@^18.3.3, @types/react-dom@^18.3.0, @vapi-ai/web@^2.3.6, @vitejs/plugin-react-swc@^3.5.0, autoprefixer@^10.4.20, class-variance-authority@^0.7.1, clsx@^2.1.1, cmdk@^1.0.0, date-fns@^3.6.0, embla-carousel-react@^8.3.0, eslint@^9.9.0, eslint-plugin-react-hooks@^5.1.0-rc.0, eslint-plugin-react-refresh@^0.4.9, globals@^15.9.0, input-otp@^1.2.4, leaflet@^1.9.4, lovable-tagger@^1.1.7, lucide-react@^0.462.0, next-themes@^0.3.0, postcss@^8.4.47, react@^18.3.1, react-day-picker@^8.10.1, react-dom@^18.3.1, react-hook-form@^7.53.0, react-leaflet@^4.2.1, react-resizable-panels@^2.1.3, react-router-dom@^6.26.2, recharts@^2.12.7, sonner@^1.5.0, tailwind-merge@^2.5.2, tailwindcss@^3.4.11, tailwindcss-animate@^1.0.7, typescript@^5.5.3, typescript-eslint@^8.0.1, vaul@^0.9.3, vite@^5.4.1, zod@^3.23.8

### Recent commits (newest first)

- Update README.md
- Update README.md
- final version
- Finished map, sorting etc.

## Key source files (fetched from GitHub, selected and truncated for size)

### package.json

```
{
  "name": "vite_react_shadcn_ts",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "build:dev": "vite build --mode development",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "@hookform/resolvers": "^3.9.0",
    "@radix-ui/react-accordion": "^1.2.0",
    "@radix-ui/react-alert-dialog": "^1.1.1",
    "@radix-ui/react-aspect-ratio": "^1.1.0",
    "@radix-ui/react-avatar": "^1.1.0",
    "@radix-ui/react-checkbox": "^1.1.1",
    "@radix-ui/react-collapsible": "^1.1.0",
    "@radix-ui/react-context-menu": "^2.2.1",
    "@radix-ui/react-dialog": "^1.1.2",
    "@radix-ui/react-dropdown-menu": "^2.1.1",
    "@radix-ui/react-hover-card": "^1.1.1",
    "@radix-ui/react-label": "^2.1.0",
    "@radix-ui/react-menubar": "^1.1.1",
    "@radix-ui/react-navigation-menu": "^1.2.0",
    "@radix-ui/react-popover": "^1.1.1",
    "@radix-ui/react-progress": "^1.1.0",
    "@radix-ui/react-radio-group": "^1.2.0",
    "@radix-ui/react-scroll-area": "^1.1.0",
    "@radix-ui/react-select": "^2.1.1",
    "@radix-ui/react-separator": "^1.1.0",
    "@radix-ui/react-slider": "^1.2.0",
    "@radix-ui/react-slot": "^1.1.0",
    "@radix-ui/react-switch": "^1.1.0",
    "@radix-ui/react-tabs": "^1.1.0",
    "@radix-ui/react-toast": "^1.2.1",
    "@radix-ui/react-toggle": "^1.1.0",
    "@radix-ui/react-toggle-group": "^1.1.0",
    "@radix-ui/react-tooltip": "^1.1.4",
    "@tanstack/react-query": "^5.56.2",
    "@vapi-ai/web": "^2.3.6",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "cmdk": "^1.0.0",
    "date-fns": "^3.6.0",
    "embla-carousel-react": "^8.3.0",
    "input-otp": "^1.2.4",
    "leaflet": "^1.9.4",
    "lucide-react": "^0.462.0",
    "next-themes": "^0.3.0",
    "react": "^18.3.1",
    "react-day-picker": "^8.10.1",
    "react-dom": "^18.3.1",
    "react-hook-form": "^7.53.0",
    "react-leaflet": "^4.2.1",
    "react-resizable-panels": "^2.1.3",
    "react-router-dom": "^6.26.2",
    "recharts": "^2.12.7",
    "sonner": "^1.5.0",
    "tailwind-merge": "^2.5.2",
    "tailwindcss-animate": "^1.0.7",
    "vaul": "^0.9.3",
    "zod": "^3.23.8"
  },
  "devDependencies": {
    "@eslint/js": "^9.9.0",
    "@tailwindcss/typography": "^0.5.15",
    "@types/node": "^22.5.5",
    "@types/react": "^18.3.3",
    "@types/react-dom": "^18.3.0",
    "@vitejs/plugin-react-swc": "^3.5.0",
    "autoprefixer": "^10.4.20",
    "eslint": "^9.9.0",
    "eslint-plugin-react-hooks": "^5.1.0-rc.0",
    "eslint-plugin-react-refresh": "^0.4.9",
    "globals": "^15.9.0",
    "lovable-tagger": "^1.1.7",
    "postcss": "^8.4.47",
    "tailwindcss": "^3.4.11",
    "typescript": "^5.5.3",
    "typescript-eslint": "^8.0.1",
    "vite": "^5.4.1"
  }
}

```

### caremap-navigator-backend/package.json

```
{
  "name": "caremap-navigator-backend",
  "version": "1.0.0",
  "main": "backend.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "cors": "^2.8.5",
    "csv-parser": "^3.2.0",
    "express": "^5.1.0",
    "fs": "^0.0.1-security"
  },
  "type": "commonjs"
}

```

### src/main.tsx

```typescript
import { createRoot } from 'react-dom/client'
import App from './App.tsx'
import './index.css'

createRoot(document.getElementById("root")!).render(<App />);

```

### src/App.tsx

```typescript
import { Toaster } from "@/components/ui/toaster";
import { Toaster as Sonner } from "@/components/ui/sonner";
import { TooltipProvider } from "@/components/ui/tooltip";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Index from "./pages/Index";
import NotFound from "./pages/NotFound";

const queryClient = new QueryClient();

const App = () => (
  <QueryClientProvider client={queryClient}>
    <TooltipProvider>
      <Toaster />
      <Sonner />
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<Index />} />
          {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
          <Route path="*" element={<NotFound />} />
        </Routes>
      </BrowserRouter>
    </TooltipProvider>
  </QueryClientProvider>
);

export default App;

```

### caremap-navigator-backend/server.js

```javascript
// ---------- server.js ----------
const express = require('express');
const fs      = require('fs');
const csv     = require('csv-parser');

const app  = express();
const PORT = 3000;
const cors = require('cors');
app.use(cors({ origin: 'http://localhost:8080' }));
let csvData = [];

/* ---------- middleware ---------- */
app.use(express.json());   // parses JSON bodies

/* ---------- load your CSV once at startup ---------- */
fs.createReadStream('./final_all_with_preferences.csv')
  .pipe(csv())
  .on('data', (row) => csvData.push(row))
  .on('end', ()  => console.log(`Loaded ${csvData.length} rows from CSV`));

/* ---------- POST /api/all ---------- */
app.post('/api/all', (req, res) => {
  console.log('GOT A CALL')
  // Expecting body = [1, 2, 3, …]
  console.log(req.body[0])
  console.log(req.headers['x-zip-code'])
  const prefCodes = Array.isArray(req.body)
    ? req.body.map(Number).filter(n => Number.isInteger(n) && n >= 1 && n <= 6)
    : [];

  // Optionally filter by zip code from header
  const zipCode = req.headers['x-zip-code'];

  let results = csvData;

  // Filter by zip if provided
  if (zipCode) {
    results = results.filter(r => String(r.zip_code) === String(zipCode));
  }

  // Filter by preference codes (any overlap counts as a match)
  if (prefCodes.length) {
    results = results.filter(r => {
      if (!r.preference_code) return false;
      const rowCodes = r.preference_code.toString().split(',')
                        .map(c => parseInt(c.trim(), 10));
      return prefCodes.some(code => rowCodes.includes(code));
    });
  }

  res.json({ data: results });
});

/* ---------- start the server ---------- */
app.listen(PORT, () =>
  console.log(`Server running at http://localhost:${PORT}`)
);

```

### caremap-navigator-backend/main.py

```python
from fastapi import FastAPI, Query, HTTPException, Request
from pydantic import BaseModel
import httpx
from fastapi.middleware.cors import CORSMiddleware
import pandas as pd
from dotenv import load_dotenv
import os
from typing import List
import json
import anthropic

# to run this file, seperately run on terminal: uvicorn main:app --reload 

load_dotenv()

CLAUDE_API_KEY = os.getenv("CLAUDE_API_KEY")
client = anthropic.Anthropic(api_key=CLAUDE_API_KEY)
df = pd.read_csv("final_with_preferences.csv")

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:8080"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

class Query(BaseModel):
    zipCode: str
    serviceTypes: List[str] #list of preferences
    languagePreference: str
    accessibilityNeeds: List[str]
    familySize: str
    specialRequirements: List[str]
    notificationPreference: str

class ChatMessage(BaseModel): #input
    role: str  # "user" or "assistant"
    content: str

class ChatRequest(BaseModel): #output response model
    messages: List[ChatMessage]


@app.post("/ask")
def search_services(query: Query):
    # Step 1: Filter by zip code directly
    filtered = df[df["zip_code"].astype(str) == query.zipCode]

    # Step 2: Further filter based on serviceTypes (which match column names like "lgbtq_friendly", etc.)
    for pref in query.serviceTypes:
        if pref in df.columns:
            filtered = filtered[filtered[pref].astype(str).str.lower() == "true"]

    # Step 3: Convert top 10 results to records
    records = filtered.to_dict(orient="records")[:10]  # or more if needed


    # Step 4: Format the input for Claude
    input_text = "\n\n".join([
        f"Name: {r.get('name')}, Address: {r.get('address')}, ZIP: {r.get('zip_code')}, Matching Preferences: {', '.join([k for k in query.serviceTypes if str(r.get(k, '')).lower() == 'true'])}"
        for r in records
    ])

    # Step 5: Build Claude prompt
    prompt = f"""
A user is looking for community services near ZIP code {query.zipCode}.
Here are their preferences:

- Service types: {', '.join(query.serviceTypes)}
- Language preference: {query.languagePreference}
- Accessibility needs: {', '.join(query.accessibilityNeeds) if query.accessibilityNeeds else 'None'}
- Family size: {query.familySize}
- Special requirements: {', '.join(query.specialRequirements) if query.specialRequirements else 'None'}
- Notification preference: {query.notificationPreference}

Based on these preferences, here are some service matches from our dataset:

{input_text}

Please summarize the key options that best meet the user's needs. Include names, addresses, zip codes, and note which preferences they match.
"""


    message = client.messages.create(
        model="claude-3-opus-20240229",
        max_tokens=500,
        temperature=0.3,
        messages=[{"role": "user", "content": prompt}]
    )

    return {
        "summary": message.content[0].text,
        "matches": records
    }

@app.post("/chat")
def chat_with_claude(chat: ChatRequest):
    response = client.messages.create(
        model="claude-3-opus-20240229",
        max_tokens=500,
        temperature=0.3,
        messages=chat.messages
    )
    return {"reply": response.content[0].text}

```

### src/pages/Index.tsx

```typescript

import React, { useState } from 'react';
import HomePage from '../components/HomePage';
import PreferencesSetup, { UserPreferences } from '../components/PreferencesSetup';
import MainApp from '../components/MainApp';

type AppState = 'home' | 'preferences' | 'app';

const Index = () => {
  const [currentState, setCurrentState] = useState<AppState>('home');
  const [userPreferences, setUserPreferences] = useState<UserPreferences | null>(null);

  const handleGetStarted = () => {
    setCurrentState('preferences');
  };

  const handlePreferencesComplete = (preferences: UserPreferences) => {
    setUserPreferences(preferences);
    setCurrentState('app');
  };

  const handleBackToHome = () => {
    setCurrentState('home');
  };

  const handleUpdatePreferences = () => {
    setCurrentState('preferences');
  };

  return (
    <div className="w-full">
      {currentState === 'home' && (
        <HomePage onGetStarted={handleGetStarted} />
      )}
      
      {currentState === 'preferences' && (
        <PreferencesSetup 
          onComplete={handlePreferencesComplete}
          onBack={handleBackToHome}
        />
      )}
      
      {currentState === 'app' && userPreferences && (
        <MainApp 
          preferences={userPreferences}
          onUpdatePreferences={handleUpdatePreferences}
        />
      )}
    </div>
  );
};

export default Index;

```

### postcss.config.js

```javascript
export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

```

### vite.config.ts

```typescript
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import path from "path";
import { componentTagger } from "lovable-tagger";

// https://vitejs.dev/config/
export default defineConfig(({ mode }) => ({
  server: {
    host: "::",
    port: 8080,
  },
  plugins: [
    react(),
    mode === 'development' &&
    componentTagger(),
  ].filter(Boolean),
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
}));

```

### eslint.config.js

```javascript
import js from "@eslint/js";
import globals from "globals";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import tseslint from "typescript-eslint";

export default tseslint.config(
  { ignores: ["dist"] },
  {
    extends: [js.configs.recommended, ...tseslint.configs.recommended],
    files: ["**/*.{ts,tsx}"],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
    },
    plugins: {
      "react-hooks": reactHooks,
      "react-refresh": reactRefresh,
    },
    rules: {
      ...reactHooks.configs.recommended.rules,
      "react-refresh/only-export-components": [
        "warn",
        { allowConstantExport: true },
      ],
      "@typescript-eslint/no-unused-vars": "off",
    },
  }
);

```

[63 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]