Skip to content

Commit 242740b

Browse files
committed
Migrate eslint
1 parent 47aa921 commit 242740b

25 files changed

+1760
-398
lines changed

.eslintrc.cjs

Lines changed: 0 additions & 19 deletions
This file was deleted.

.vscode/settings.json

Lines changed: 0 additions & 12 deletions
This file was deleted.

docs/plan.md

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
Technical Roadmap: AI Chatbot, User Tiers, and Billing
2+
Architecture Overview
3+
4+
┌─────────────────────────────────────────────────────────────────┐
5+
│ User Tiers │
6+
├─────────────┬─────────────────────┬─────────────────────────────┤
7+
│ Public │ Paid (Registered) │ Admin │
8+
├─────────────┼─────────────────────┼─────────────────────────────┤
9+
│ • View map │ • View map │ • Full CRUD │
10+
│ • No desc │ • With descriptions │ • Publish state │
11+
│ • No chat │ • AI chatbot │ • Analytics │
12+
│ • Cached │ • Saved filters(?) │ • AI chatbot │
13+
└─────────────┴──────────┬──────────┴─────────────────────────────┘
14+
15+
┌────────────────┼────────────────┐
16+
▼ ▼ ▼
17+
┌─────────┐ ┌────────────┐ ┌──────────┐
18+
│ Stripe │ │ Firebase │ │ Firebase │
19+
│Checkout │───▶│ Auth │───▶│Firestore │
20+
└─────────┘ └────────────┘ │Permissions│
21+
└──────────┘
22+
23+
24+
┌─────────────────────┐
25+
│ Firebase Functions │
26+
│ (2nd Gen) │
27+
│ • AI Chat endpoint │
28+
│ • Stripe webhooks │
29+
└─────────────────────┘
30+
31+
32+
┌─────────────────────┐
33+
│ Vercel AI SDK │
34+
│ + Claude/GPT │
35+
└─────────────────────┘
36+
37+
Phase 1: User Tiers & Permissions Infrastructure
38+
39+
Goal: Establish 3-tier user system before adding features that depend on it.
40+
41+
1.1 Extend Permissions Schema
42+
43+
// src/types.ts - new Permission type
44+
interface Permission {
45+
isAdmin: boolean
46+
isPaid: boolean // NEW: has active subscription
47+
countryCodes: string[]
48+
stripeCustomerId?: string // NEW: for Stripe integration
49+
subscriptionStatus?: 'active' | 'canceled' | 'past_due'
50+
createdAt: Timestamp
51+
}
52+
53+
1.2 Create 3 State Files
54+
File Audience Contents
55+
publicState.json Public Incidents without descriptions
56+
state.json Paid users Incidents with descriptions
57+
adminCheckpointState.json Admins Full data + timestamps
58+
59+
1.3 Modify Data Fetching (src/utils.ts, src/context/DBContext.tsx)
60+
61+
Check isPaid from Permissions collection
62+
Route to appropriate state file
63+
Update loadDB() to handle 3 tiers
64+
65+
1.4 Update Firestore Rules
66+
67+
// firestore.rules
68+
match /Incidents/{incidentId} {
69+
allow read: if request.auth != null &&
70+
getUserPermissions(request.auth.uid).isPaid;
71+
}
72+
73+
Files to modify:
74+
75+
src/types.ts
76+
src/utils.ts
77+
src/context/DBContext.tsx
78+
firestore.rules
79+
src/pages/PublishAdmin.tsx (generate 3 state files)
80+
81+
Phase 2: Stripe Billing Integration
82+
83+
Goal: Self-service signup with Stripe Checkout gating paid access.
84+
85+
2.1 New Dependencies
86+
87+
pnpm add stripe @stripe/stripe-js
88+
pnpm add -D @types/stripe
89+
90+
2.2 Firebase Functions Setup
91+
92+
cd functions
93+
npm init -y
94+
npm install firebase-functions firebase-admin stripe
95+
96+
2.3 Stripe Webhook Handler (Firebase Function)
97+
98+
// functions/src/stripe.ts
99+
export const stripeWebhook = onRequest(async (req, res) => {
100+
const event = stripe.webhooks.constructEvent(...)
101+
102+
switch (event.type) {
103+
case 'checkout.session.completed':
104+
// Create Firebase user if needed
105+
// Set isPaid: true in Permissions
106+
break
107+
case 'customer.subscription.deleted':
108+
// Set isPaid: false
109+
break
110+
}
111+
})
112+
113+
2.4 Checkout Flow
114+
115+
User clicks "Subscribe" → redirect to Stripe Checkout
116+
Stripe collects payment + creates customer
117+
Webhook fires → Firebase Function creates/updates Permission doc
118+
User redirected back → Firebase Auth signs them in
119+
App reads isPaid: true → grants access
120+
121+
2.5 New Components
122+
123+
src/pages/Pricing.tsx - pricing page with Stripe button
124+
src/components/PaywallGate.tsx - wrap paid features
125+
126+
Files to create:
127+
128+
functions/ directory (Firebase Functions)
129+
functions/src/stripe.ts
130+
src/pages/Pricing.tsx
131+
src/components/PaywallGate.tsx
132+
133+
Files to modify:
134+
135+
src/App.tsx (new routes)
136+
firebase.json (functions config)
137+
138+
Phase 3: AI Chatbot
139+
140+
Goal: Natural language filter construction + data Q&A + graph generation.
141+
142+
3.1 Dependencies
143+
144+
pnpm add ai @ai-sdk/anthropic # or @ai-sdk/openai
145+
146+
For Firebase Functions:
147+
148+
cd functions
149+
npm install ai @ai-sdk/anthropic
150+
151+
3.2 AI Tools Schema
152+
153+
The chatbot needs tools that mirror your filter system. Based on src/filters/filterReducer.ts:
154+
155+
// functions/src/ai/tools.ts
156+
const tools = {
157+
constructFilter: {
158+
description: 'Build a filter to show/hide incidents on the map',
159+
parameters: z.object({
160+
type: z.enum(['category', 'country', 'date', 'desc', 'latlong', 'not', 'or']),
161+
state: z.any() // Varies by filter type
162+
})
163+
},
164+
165+
queryIncidents: {
166+
description: 'Answer questions about incident data',
167+
parameters: z.object({
168+
question: z.string(),
169+
filters: z.array(filterSchema).optional()
170+
})
171+
},
172+
173+
generateGraph: {
174+
description: 'Create a visualization of incident data',
175+
parameters: z.object({
176+
type: z.enum(['line', 'pie', 'bar']),
177+
metric: z.enum(['count', 'byCategory', 'byCountry', 'byDate']),
178+
filters: z.array(filterSchema).optional()
179+
})
180+
}
181+
}
182+
183+
3.3 Firebase Function Endpoint
184+
185+
// functions/src/ai/chat.ts
186+
import { streamText } from 'ai'
187+
import { anthropic } from '@ai-sdk/anthropic'
188+
189+
export const chatEndpoint = onRequest(async (req, res) => {
190+
// Verify user is paid via Firebase Auth token
191+
const user = await verifyAuth(req)
192+
if (!user.isPaid) return res.status(403).send('Subscription required')
193+
194+
const result = await streamText({
195+
model: anthropic('claude-sonnet-4-20250514'),
196+
system: `You help users explore crime incident data in South America...`,
197+
messages: req.body.messages,
198+
tools
199+
})
200+
201+
// Stream response back
202+
result.pipeTextStreamToResponse(res)
203+
})
204+
205+
3.4 Frontend Chat Component
206+
207+
// src/components/ChatBot.tsx
208+
import { useChat } from 'ai/react'
209+
210+
function ChatBot() {
211+
const { messages, input, handleSubmit } = useChat({
212+
api: '/api/chat', // Firebase Function URL
213+
})
214+
215+
// Handle tool calls to update filters
216+
useEffect(() => {
217+
messages.forEach(msg => {
218+
if (msg.toolInvocations) {
219+
msg.toolInvocations.forEach(tool => {
220+
if (tool.toolName === 'constructFilter') {
221+
dispatch({ type: 'ADD_FILTER', filter: tool.result })
222+
}
223+
})
224+
}
225+
})
226+
}, [messages])
227+
}
228+
229+
3.5 Graph Generation
230+
231+
When the AI calls generateGraph, the frontend receives props to render:
232+
233+
// Tool result from AI
234+
{
235+
type: 'line',
236+
metric: 'byDate',
237+
data: [...], // AI queries data and returns
238+
title: 'Incidents Over Time in Colombia'
239+
}
240+
241+
// Frontend renders
242+
<LineGraph {...toolResult} />
243+
244+
Files to create:
245+
246+
functions/src/ai/chat.ts
247+
functions/src/ai/tools.ts
248+
functions/src/ai/systemPrompt.ts
249+
src/components/ChatBot.tsx
250+
src/components/ChatMessage.tsx
251+
src/hooks/useChat.ts (if customizing beyond ai/react)
252+
253+
Files to modify:
254+
255+
src/App.tsx (add chat UI)
256+
src/pages/Map.tsx (integrate chat panel)
257+
src/filters/filterReducer.ts (may need BATCH_ADD_FILTERS action)
258+
259+
Phase 4: Integration & Polish
260+
261+
4.1 Chat-to-Filter UX
262+
263+
Chat suggests a filter → user confirms → filter applied
264+
Show preview of what the filter will do before applying
265+
"Undo" capability for AI-applied filters
266+
267+
4.2 Persisted Conversations
268+
269+
Store chat history in Firestore per user
270+
Resume conversations across sessions
271+
272+
4.3 Rate Limiting
273+
274+
// functions/src/middleware/rateLimit.ts
275+
// Limit paid users to X messages/day to control costs
276+
277+
4.4 Cost Monitoring
278+
279+
Log token usage per user
280+
Dashboard for admin to monitor AI costs
281+
Alert if approaching budget threshold
282+
283+
Key Files Summary
284+
File Changes
285+
src/types.ts Add Permission.isPaid, chat types
286+
src/filters/filterReducer.ts Already well-structured for AI
287+
src/context/DBContext.tsx 3-tier data loading
288+
src/utils.ts New state file logic
289+
firestore.rules Paid user read access
290+
functions/src/stripe.ts Webhook handler
291+
functions/src/ai/chat.ts Chat endpoint
292+
src/components/ChatBot.tsx Chat UI
293+
src/pages/Pricing.tsx Stripe checkout
294+
Dependencies to Add
295+
296+
Frontend:
297+
298+
{
299+
"ai": "^3.x",
300+
"@ai-sdk/anthropic": "^0.x",
301+
"@stripe/stripe-js": "^2.x"
302+
}
303+
304+
Functions:
305+
306+
{
307+
"firebase-functions": "^4.x",
308+
"firebase-admin": "^11.x",
309+
"stripe": "^14.x",
310+
"ai": "^3.x",
311+
"@ai-sdk/anthropic": "^0.x"
312+
}

0 commit comments

Comments
 (0)