Commit graph

27 commits

Author SHA1 Message Date
3cfcbdc0ca fix: garment upload MIME/proxy, log edit time pre-fill, date-ist hardening, wardrobe camera+gallery
- /api/img: add garments/ to ALLOWED_PREFIXES so garment images proxy correctly
- garments upload: resolve Android empty MIME type from file extension; return
  /api/img proxy URLs instead of raw pub-*.r2.dev (blocked by Cloudflare Bot Mgmt)
- garments route + [id] route: toDto() now builds /api/img?key= proxy URLs
- date-ist.ts: add toUTCDate() helper -- strings without Z/offset treated as UTC,
  preventing browser local-time misinterpretation; used in fmtTime, fmtDate, dateIST
- LogModal: add editTime to SmartDefault; pre-fill time picker (custom preset) when
  editing an existing log instead of defaulting to now
- activity page: pass editTime: log.loggedAt in handleEdit so LogModal pre-fills
- wardrobe/add: explicit Camera and Gallery buttons via separate hidden inputs
  (one with capture=environment for direct camera, one without for media picker)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 00:42:04 +05:30
f953963b3b Fix memories disappearing + always-processing state
Three bugs fixed:

1. Vision failure was marking memories as 'failed', triggering orphan
   cleanup that permanently deleted them. Vision is optional — on error,
   now marks 'ready' so the image stays visible without AI captions.

2. Thumbnail failure was blocking vision from running (rejected promise
   swallowed the chain). Fixed by catching thumbnail error separately so
   processMemoryVision always executes and always sets a final status.

3. /api/img proxy was rejecting thumbnail keys (families/{id}/thumbnails/…)
   because 'families/' was not in ALLOWED_PREFIXES. Added it.

Also: replaced the full-bleed 'Processing…' overlay with a small corner
badge so the uploaded photo is visible immediately after upload.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 22:37:46 +05:30
a3a0ddf3c9 Proxy R2 images through /api/img to fix 503 from Cloudflare Bot Management
pub-xxx.r2.dev returns 503 for cross-origin sub-resource requests (img tags,
fetch) due to Cloudflare Bot Management on the r2.dev dev domain. Direct
browser navigation works but programmatic loading fails, so all uploaded
images appeared as placeholders after upload.

Fix: route all image display through a same-origin /api/img?key=... proxy that
fetches from R2 via the S3 API server-side. API responses (profile, children,
memories) now return proxy URLs. After upload, UI state is updated with proxy
URLs directly rather than raw R2 URLs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 21:35:25 +05:30
309fd5aa29 fix(timezone): all date/time display now uses IST (Asia/Kolkata)
New src/lib/date-ist.ts utility:
- hourIST()   — current hour in IST (replaces new Date().getHours() on server)
- isTodayIST() / dateIST() — IST-aware "today" comparisons
- fmtTime()   — time display with timeZone: "Asia/Kolkata"
- fmtDate()   — date display with timeZone: "Asia/Kolkata"
- dayLabel()  — "Today" / "Yesterday" / "Mon, 26 May" in IST

Applied across: home (greeting, today-summary, log times),
activity (day grouping, bar chart, log times), ai, growth,
milestones, settings — eliminating Finland-timezone artifacts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 10:05:15 +05:30
0a9def36bf feat(quota): enforce 1-baby limit on free plan
- quota.ts: checkChildLimit() mirrors checkMemberLimit() using families.max_children
- POST /api/children: returns 403 child_limit_reached when free family is at limit
- ChildLimitBanner: new banner component for the family page
- /family page: shows banner + hides Add Baby button when at limit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 00:52:23 +05:30
0c7f37fd12 feat(quota): storage quota + family-member limits for free tier
Feature A — Storage quota (1 GiB per family):
- src/lib/quota.ts: enforcement library with pure functions (fully unit-tested)
  and DB-bound helpers; isPaidFamily() is the single payment abstraction gate
- src/lib/format-bytes.ts: extracted formatBytes() — safe for client imports
- POST /api/upload: quota check before presigned URL issuance (HTTP 402 + reason code)
- POST /api/memories/[id]/confirm: HeadObject reconciles actual R2 size; deletes
  over-quota objects and marks row failed rather than silently exceeding limit
- GET /api/storage-usage: storage info endpoint for UI meter
- src/components/StorageMeter.tsx: meter bar + StorageQuotaBanner + MemberLimitBanner
- memories/page.tsx: quota banner, FAB disabled (⊘) when exceeded, compact meter in header
- settings/page.tsx: always-visible StorageMeter + MemberLimitBanner in invite section

Feature B — Member limit (2 per family, free tier):
- invites/route.ts: replaced ad-hoc inline check with checkMemberLimit() from quota lib
  Structured 403 response: { reason, currentCount, limit }
- Freeze rule: paid→free downgrade leaves all members intact; only new invites blocked

Migration:
- drizzle/0007_subscription_status.sql: ADD COLUMN subscription_status varchar(20)
- debug-migration/route.ts: step added for hot-apply without full redeploy
- src/db/schema/family.ts: subscriptionStatus field added to Drizzle schema

Tests: 44 unit tests in src/__tests__/quota.test.ts, all passing
- Pure function tests (no DB): isPaidFamily, wouldExceedQuota, isAtMemberLimit, formatBytes
- DB-bound tests (mocked @/db): getFamilyStorageUsage, checkStorageQuota,
  checkMemberLimit, getStorageInfo, tenant isolation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 23:21:11 +05:30
31b4a9480a fix(email): use tia@tia.manohargupta.com + auto-fallback to shared domain
- Primary sender: tia@tia.manohargupta.com (verified subdomain in Resend)
- sendWithFallback() retries with onboarding@resend.dev if Resend rejects
  the primary domain (covers the window while SPF is still propagating)
- Both sendFamilyInviteEmail() and sendVerificationEmail() use the fallback

Update EMAIL_FROM in Dokploy to: Tia <tia@tia.manohargupta.com>

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 16:01:55 +05:30
8a3da6a5a6 fix(email): fallback to onboarding@resend.dev until custom domain verified in Resend 2026-05-24 15:18:41 +05:30
b01e0596c1 feat(invites): auto-send invite email via Resend + register migration in journal
- Add sendFamilyInviteEmail() to email.ts using existing Resend setup
- Wire into POST /api/invites — fetches inviter name + family name, sends
  warm invite email with Accept Invitation button linking to /invite/{token}
- Email is non-fatal: invite is created even if email send fails
- Register 0006_family_invites_missing_cols in _journal.json so Dokploy
  auto-applies the migration on next deploy

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 14:24:31 +05:30
6bdaade777 feat: email verification + Google OAuth
- Signup now creates unverified users and sends a verification email
  (Resend); dev falls back to [VERIFY-LINK] console log
- /api/auth/verify-email: single-use token handler, mints tia_session
  on success, redirects to /onboarding
- /api/auth/resend-verification: rate-limited (3/hr), enumeration-safe
- Sign-in gated on email_verified — unverified accounts get 403 with
  needsVerification flag so the UI can show the resend button
- Google OAuth via arctic v3: PKCE + state anti-CSRF, find-or-create
  user, writes accounts row, mints tia_session
- Login page: Google button, check-email screen, resend link on 403
- drizzle/0005_email_verification.sql: creates email_verifications
  table + backfills all existing users as verified (runs automatically
  on container start before app boots)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 12:56:02 +05:30
a14d8e7043 Fix activity page repetition + 4-day strip layout + guideline accuracy
- Remove redundant daily summary bar (Today chip in 4-day strip covers it)
- 4-day strip: reversed to oldest→newest order (Wed→Thu→Yest.→Today)
- 4-day strip: switched from flex to grid grid-cols-4 so all 4 chips
  fill the full row width evenly instead of floating left
- Today chip highlighted in rose-400 to stand out from past days
- Guidelines: corrected 9-12 mo (feeds 3→4, sleep 12→14h, diapers 3→4)
  per AAP; 12-18 mo sleep 11→13h, diapers 3→4; 18-24 mo sleep 11→12h,
  diapers 2→3; all now match mid-range AAP recommendations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 23:35:32 +05:30
1994725101 feat(wardrobe): add complete wardrobe feature (W0–W9)
Schema (W0):
- Add garments, garment_wears, outfits tables with Drizzle migrations
- Drizzle migrations 0001 (garments/wears) and 0002 (outfits) auto-apply on deploy
- RLS policies in drizzle/manual/06-wardrobe-rls.sql (apply via superuser in prod)

API (W1–W9):
- POST /api/garments/upload — direct upload to R2 garments/ prefix with sharp thumbnail
- POST /api/garments/tag — vision tagging via LiteLLM, defensive parse, category validated
- GET/POST /api/garments — list with composable filters, create
- GET/PATCH/DELETE /api/garments/[id] — detail, edit, delete
- POST /api/garments/[id]/wear — log worn date
- GET /api/garments/outgrowth — pure SQL, explicit size ordering (no lexicographic sort)
- GET /api/garments/packing — active garments grouped by category
- GET /api/garments/outfit — Open-Meteo weather + deterministic outfit pairing, no LLM
- GET/POST /api/garments/outfits + DELETE [id] — saved outfits

Pages:
- /wardrobe — grid with status/category/size/season filters + outgrowth nudge
- /wardrobe/add — 3-step capture→vision→form, size required, batch-friendly
- /wardrobe/[id] — detail/edit/status lifecycle + wear history
- /wardrobe/packing — packing checklist by category
- /wardrobe/outfit — weather-aware suggestions with shown basis
- /wardrobe/saved-outfits — view/delete saved combinations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 18:09:22 +05:30
96d28cbadc refactor: full codebase sweep — shared types, utilities, component splits
New foundations:
- src/types/index.ts — shared domain types (Child, Log, GrowthRecord, Medicine,
  Dose, Allergy, Visit, Illness, Vaccination, AIChat, ChatSession, Goal)
- src/lib/formatting.ts — calculateAge, formatAge, formatTimeAgo (eliminates
  3 duplicate implementations spread across page.tsx and growth/page.tsx)
- src/lib/api.ts — typed fetch helpers (api.get/post/patch/delete) with
  consistent error handling; replaces manual fetch boilerplate

New shared components:
- src/components/PageHeader.tsx — reusable back-link + title header
- src/components/TabBar.tsx — horizontal pill tab bar
- src/components/CalendarView.tsx — extracted from activity/page.tsx (was ~170 inline lines)
- src/components/medical/ — medical page split into 5 focused tab components:
  VaccineTab, MedicineTab, AllergyTab, VisitTab, IllnessTab

Pages updated:
- medical/page.tsx: 1029 → 42 lines (thin shell wiring the 5 tab components)
- activity/page.tsx: uses CalendarView + shared Log type + api.ts
- growth/page.tsx: uses shared GrowthRecord/Goal types + formatAge; fixes
  `any` catch clauses; fixes undefined → null in Chart.js dataset values
- page.tsx (home): uses shared Log/AIChat/ChatSession types + formatTimeAgo/
  calculateAge from formatting.ts; removes inline type definitions
- ai/page.tsx: uses shared AIChat/ChatSession types
- FamilyProvider.tsx: uses shared Child type; fixes `c: any` mapping

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 21:37:39 +05:30
3ebb3055a5 feat(logging): time presets, FAB on activity, today summary, smart defaults
- Extract offline queue to src/lib/offline-queue.ts
- Extract shared LogModal with time presets (Just now/5/15/30min/Custom)
  and smart default pre-fill from last log of same type
- Replace ActivityScroller with TodaySummary (today's counts + last time)
- Fix activity page: GET /api/logs without type param now returns all logs merged
- Fix field naming: log.loggedAt / log.amount (camelCase throughout)
- Add FAB to activity page for zero-navigation quick logging
- Recent Activity shows 5 most recent entries with correct field names

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 11:21:00 +05:30
291eb4793b feat(g5-g6): age-aware UX + mama affiliate page
G5 — Age-Aware UX:
- useStageCheck hook: maps birth date → BabyStage (newborn/infant/sitter/crawler/toddler/walker)
- Time-of-day fast-log suggestion chip on home page (time × stage matrix)
- Milestones page: 25 WHO/AAP milestones, category filter, progress bar, inline date picker
- Milestones API: GET (merged definitions + achievements), POST (upsert), DELETE (un-mark)
- DB: milestone_achievements table with unique(child_id, milestone_key)
- Milestones 🌟 added to menu

G6 — Mama Affiliate Page:
- member_profiles, recommended_products, product_clicks tables
- /api/profile CRUD (GET/PUT), /api/profile/products (GET/POST/PATCH/DELETE)
- Public routes: /api/profile/[slug] and /api/profile/[slug]/click (IP hashed)
- /settings/profile: slug + bio editor, product list with ↑↓ reorder + click counts
- /m/[slug]: beautiful public page (gradient bg, product grid, Shop → click tracking)
- Settings page link to profile setup

DB migrations: 0014_milestones, 0015_affiliate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 00:59:17 +05:30
c2cabc01d3 feat(g1-g4): design system, memories pipeline, medical tracking, AI brain
G1 — Design System: 14 UI primitives (Button, Card, Modal, Sheet, Input,
Textarea, Select, EmptyState, LoadingShimmer, ConfirmDialog, WashiTape,
Badge, Avatar, Tabs), PageTransition with Framer Motion, sun/moon CSS vars,
Caveat font, /dev/components visual showcase.

G2 — Memories Pipeline: R2 presigned uploads, Sharp thumbnail generation,
LiteLLM vision captions + pgvector embeddings, CSS masonry gallery with
infinite scroll, private toggle, semantic search fallback to ILIKE.

G3 — Medical: dose log + correction audit trail, IAP vaccine bulk import,
emergency escalation page, pediatrician phone in settings.

G4 — AI Brain: keyword guardrail → LLM classifier → structured DB tool-use
(7 tools) → memory search → general parenting handler; ai_usage table;
22-case medical bypass safety test suite.

DB migrations: 0011_memories, 0012_medical_doses, 0013_ai_usage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 17:48:34 +05:30
85d313bc86 fix(admin): use correct column name 'expires' in admin_sessions queries
verifyAdminSession() and requireAdmin() both used expires_at but the
admin_sessions table column is named expires — causing every session
check to silently fail and always redirect to /admin-login.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 12:22:17 +05:30
fc0e75b5ad fix(admin): scope FamilyProvider out of admin routes, ensure cookies on admin fetches
Root causes:
- tia_admin_session is httpOnly so document.cookie could never read it → all
  client-side cookie checks always failed and redirected before any data fetched
- Sub-pages used localStorage.getItem("admin_token") which was never stored,
  and passed Authorization: Bearer null headers the server ignores

Fixes:
- FamilyProvider: use usePathname() hook instead of window.location.pathname
- admin/layout.tsx: rewrite as server component using verifyAdminSession()
  (new lib/admin-auth.ts helper that uses next/headers cookies()) → server-side
  redirect to /admin-login if session invalid; extract sidebar to AdminSidebar.tsx
- admin/page.tsx: remove broken document.cookie guard (layout handles auth now)
- admin-login/page.tsx: replace document.cookie check with GET /api/admin/auth call
- All 7 admin sub-pages: remove localStorage guard, remove Authorization: Bearer
  headers, add credentials: include to every fetch call

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 12:16:10 +05:30
cb7d9411ff Fix JOIN - sessions directly to family_members 2026-05-17 00:38:15 +05:30
7b6f033d42 Fix UUID join in auth.ts 2026-05-17 00:35:49 +05:30
a43bb060ff Fix UUID join types 2026-05-17 00:31:32 +05:30
a54f30ddcb Security hardening - all 8 patches applied
Patch 1: Add requireFamily to chat route
Patch 2: Add requireFamily to family routes
Patch 3: Create admin-auth.ts, apply to all admin routes
Patch 4: Delete debug and migrate routes, update middleware
Patch 5: Create audit_log table and schema
Patch 6: Create password reset flow (reset-request, reset-confirm)
Patch 7: Replace with real HTTP security tests
Patch 8: RLS migrations already exist (01-app-role, 02-enable-rls)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 23:59:43 +05:30
2196c3d07d Security hardening: auth, bcrypt, rate-limiting, RLS, audit 2026-05-16 23:11:01 +05:30
4cf886ea43 Add security libs: auth, audit, rate-limit, scoped db 2026-05-16 23:10:56 +05:30
0865706a94 Add WHO growth standards with percentile tracking
- Add head circumference to WHO standards (boys & girls 0-24 months)
- Update growth API to return WHO standards with records
- Update growth page to show percentile rankings
- Add head circumference input to form
- Use FamilyProvider instead of hardcoded childId
- Show percentile (e.g., "50th-85th") for each measurement

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 18:07:41 +05:30
bfc1543b1c Add administered date for vaccinations and WHO growth benchmarks
Vaccinations:
- Allow custom date input when marking vaccine as given
- Show actual administered date alongside scheduled due date

Growth:
- Add WHO child growth standards data (boys/girls)
- Show age-based benchmarks on Growth page
- Display percentile ranges for weight and height
- Show latest measurement compared to standards

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 16:17:57 +05:30
967e00c4fa Add smart onboarding to Activity page
- Pediatric guidelines data with age-based schedules
- Show child's age and benchmarks on Activity page
- AI history generation via /api/history
- Generate button to auto-populate past logs from birth

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 16:12:23 +05:30