- Add PATCH endpoint to set user passwords - Add password modal UI in admin panel - Update CLAUDE.md with latest features Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
8.4 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Commands
# Development
pnpm dev # Start dev server at http://localhost:3000
pnpm build # Production build (uses Turbopack by default)
pnpm start # Start production server
# Note: If Turbopack fails, use: pnpm build -- --webpack
# Database (direct SQL or docker)
docker-compose -f docker-compose.dev.yml exec db psql -U postgres -d tia
Note: Running from /Users/manohar_air/MyProjects/Tia/tia directory.
Architecture
Tech Stack
- Framework: Next.js 16 with App Router (src/app/)
- Database: PostgreSQL 16 with pgvector + Drizzle ORM
- Auth: Database sessions with httpOnly cookies
- AI: LiteLLM gateway → MiniMax model (minimax-2.7)
- Storage: Cloudflare R2 for media uploads
- Styling: Tailwind CSS v4
Project Structure
src/
├── app/ # Next.js App Router pages
│ ├── api/ # API routes (auth, logs, ai, growth, etc.)
│ ├── page.tsx # Home (Quick Log + AI card)
│ ├── ai/ # AI chat page with sidebar
│ ├── medical/ # Vaccination tracking
│ ├── growth/ # Growth charts
│ ├── memories/ # Photo gallery
│ ├── menu/ # Navigation menu
│ ├── onboarding/ # First-time setup
│ ├── settings/ # Settings with theme picker
│ ├── login/ # User login (email + password)
│ ├── admin/ # Admin panel
│ └── admin-login/ # Admin login (separate)
├── ThemeProvider.tsx # Theme context (light/dark/system/time)
├── FamilyProvider.tsx # Family/child context (resolves from session)
drizzle/ # Database migrations
docs/ # Design docs
Database
- Migrations: SQL files in
drizzle/(not using drizzle-kit push) - Apply:
psqldirectly or via docker-compose exec - RLS: Row-level security for multi-family isolation
Data Models
- Family: Parent account container
- Members: Adults in family (mom, dad, etc.) via
family_members - Children: Baby profiles with birth date
- Sessions: Login sessions with httpOnly cookies
- Logs: Feed, sleep, diaper entries with timestamps
- Vaccinations: IAP schedule tracking
- Growth: Weight/height over time
- Memories: Photos with R2 storage
- Chat Sessions: User conversations with AI (chat_sessions, chat_messages)
Key Patterns
ThemeProvider: Wrap app in ThemeProvider from layout.tsx. Use useTheme() hook in components.
import { useTheme } from "./ThemeProvider";
const { theme, toggle, setMode } = useTheme();
// theme: "light" | "dark"
// mode: "light" | "dark" | "system" | "time"
FamilyProvider: Resolves family from database session on login.
import { useFamily } from "./FamilyProvider";
const { familyId, familyName, child, children, tier, memberCount } = useFamily();
// familyId: string | null
// familyName: string | null (from session)
// child: Child | null
// children: Child[]
// tier: "free" | "pro"
// memberCount: number (from family_members table)
Offline Queue: Uses localStorage (tia_offline_queue) for failed API calls, retries when online.
Chat Sessions: Stored in localStorage (tia_chat_sessions) - shared between home page AI card and /ai page. Database tables: chat_sessions, chat_messages.
API Routes: Return standard JSON { success: true, items: [...] } format for lists.
AI Integration:
- Route:
/api/ai→ LiteLLM athttps://llm.manohargupta.com - Model:
minimax-2.7 - See
/docs/debugging.mdfor troubleshooting
Authentication (Email + Password)
- User visits
/loginwith email + password (or signs up for new account) - API
/api/auth/signinverifies password hash and creates session insessionstable - Session token stored in httpOnly cookie (NOT localStorage!)
- Password stored with simple hash in
users.password_hash
Tables Used
- users: User accounts (email, name)
- families: Family accounts (name, tier, limits)
- family_members: Links users to families (user_id, family_id, role)
- children: Child profiles (name, birth_date, family_id)
- sessions: Login sessions (session_token, user_id, expires)
NEVER use localStorage for:
- authentication tokens
- family_id after login
- Any data that should persist across devices
localStorage Acceptable For:
- Theme preference (user-specific display only)
- Temporary cache (offline queue for retry)
- Chat sessions local cache (synced from database)
Admin Panel
Access at: /admin-login (username: admin, password: admin123)
Pages
/admin- Dashboard with clickable stat cards/admin/families- Manage families (create, view/add/remove members, set tier)/admin/users- Manage users (add to family, password status, delete)/admin/children- Manage children/admin/revenue- Revenue analytics/admin/analytics- Feature usage/admin/support- Support tickets/admin/settings- Platform settings
Data Storage Consistency
RULE: All user data must persist to database, NOT localStorage
| Data Type | Storage | API Key | Persists After Refresh | Persists After Logout |
|---|---|---|---|---|
| Children | Database | /api/children |
✅ Yes | ✅ Yes |
| Activity Logs | Database | /api/logs |
✅ Yes | ✅ Yes |
| Vaccinations | Database | /api/vaccinations |
✅ Yes | ✅ Yes |
| Growth Records | Database | /api/growth |
✅ Yes | ✅ Yes |
| User Profile | Database | /api/auth/profile |
✅ Yes | ✅ Yes |
| Memories/Photos | Database + R2 | /api/upload |
✅ Yes | ✅ Yes |
| Auth Session | Database + Cookie | /api/auth/signin |
✅ Yes | ✅ No |
| Theme | localStorage | tia_theme |
✅ Yes | ✅ Yes |
| Chat Sessions | Database | /api/chat |
✅ Yes | ✅ Yes |
| Offline Queue | localStorage | tia_offline_queue |
✅ Yes | ❌ No |
R2 Storage (Cloudflare)
Setup
- Create bucket in Cloudflare Dashboard → R2
- Create API token with "Object Read & Write" permissions
- Enable Public Development URL in bucket settings (gives pub-*.r2.dev URL)
API Endpoint Format
The S3 API endpoint is: https://<accountId>.r2.cloudflarestorage.com
For your bucket named "tia":
- Account ID:
e71f22a2f8614fb3ba6d9b28a264d8ce - S3 Endpoint:
https://e71f22a2f8614fb3ba6d9b28a264d8ce.r2.cloudflarestorage.com - Public URL:
https://pub-37a76fd657c94d1dbc521a109c087a11.r2.dev(no bucket name in path!)
Code Example
const client = new S3Client({
region: "auto",
endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
credentials: { accessKeyId, secretAccessKey },
});
// List objects
const command = new ListObjectsV2Command({ Bucket: "tia" });
const res = await client.send(command);
// Get presigned upload URL
const command = new PutObjectCommand({ Bucket: "tia", Key: key, ContentType });
const url = await getSignedUrl(client, command, { expiresIn: 3600 });
Key Learnings
- S3 API endpoint does NOT include bucket name:
https://...r2.cloudflarestorage.com - Public URL does NOT include bucket path:
https://pub-...r2.dev(NOT/tia/...) - CORS needs GET and PUT methods for uploads
- ListObjects needs bucket name in command, not endpoint
Environment Variables
Set in .env.local for development, or in Dokploy dashboard for production.
Required:
DATABASE_URL- PostgreSQL connectionAUTH_SECRET- NextAuth secretR2_ACCOUNT_ID- Cloudflare R2 account IDR2_ACCESS_KEY_ID- R2 access keyR2_SECRET_ACCESS_KEY- R2 secret keyR2_BUCKET_NAME- R2 bucket name (e.g., "tia")R2_PUBLIC_URL- Public R2 URL (e.g.,https://pub-...r2.dev)
Optional for AI:
LITELLM_BASE_URL- AI gateway URLLITELLM_API_KEY- AI API key
Known Issues
Turbopack Parsing Issue
Some files may cause "Unterminated regexp literal" errors when building with Turbopack. If build fails:
-
Try building with Webpack instead:
pnpm build -- --webpack -
The issue is in SWC parser - avoid patterns like
name: "TT/Td"in arrays as the/can be interpreted as regex -
Apply fixes one change at a time between builds - cumulative changes can confuse Turbopack's cache