- 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>
CLAUDE.md:
- Add RESEND_API_KEY, EMAIL_FROM, NEXT_PUBLIC_APP_URL to required env vars
- Document DB migration pattern (journal + hot-fix via debug-migration POST)
- Document R2 3-step proxy upload pattern (CORS note)
- Document users.image (NOT avatar_url) and two separate photo features
- Document admin auth server-component pattern
- Document family_invites fix, invite flow, cancel invite
- Full data storage table with all localStorage keys
Invite route:
- Return emailStatus in POST response so caller can see if Resend fired
or why it failed (noKey / error message)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New DELETE /api/invites/[id] endpoint — only the owning family can delete
- Settings page shows expiry date on each pending invite
- Cancel button removes invite instantly from list (optimistic UI)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add ALTER TABLE steps to debug-migration POST so the columns can be applied
immediately via the running app without waiting for a full Dokploy redeploy.
Also revert invites routes to use display_name/accepted_at now that the
hot-fix path exists.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The family_invites migration hasn't run yet on production. Work around by:
- Removing display_name from INSERT and SELECT (optional field anyway)
- Removing accepted_at IS NULL filter from GET and accept queries
- DELETE the invite row on accept instead of marking accepted_at — keeps
invites single-use without needing the extra column
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
Both columns are referenced by the invite API but were never in the table,
causing "column does not exist" errors when inviting family members.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
users.avatar_url doesn't exist — the column is `image`. Querying/updating
a non-existent column caused a SQL error on every profile load (blank name
& email) and on every avatar save/delete.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Switch from broken FormData POST to 3-step flow: POST init → PUT /api/upload proxy → PATCH save
- Add remove photo option that clears DB and deletes R2 object (DELETE /api/auth/avatar)
- Add deleteOldAvatar() helper that cleans up old R2 object on every upload
- No orphaned objects in R2, no wasted storage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Tapping avatar when a photo exists shows a mini menu: Change photo / Remove photo
- Tapping avatar when no photo exists goes straight to file picker (no menu)
- handleRemovePhoto: PATCHes imageUrl=null, deletes file from R2, clears UI
- PATCH /api/children/[id]: fetches old image_url before update, deletes the
old R2 object (profiles/ prefix only) when it changes — no more orphaned files
- updateChildImage in FamilyProvider now accepts string | null
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Direct PUT to R2 presigned URL is cross-origin — browser blocks it.
Route the upload through the existing PUT /api/upload server proxy instead,
same pattern used for memories. Also return `key` from children POST so
the proxy call has the R2 object key without needing the presigned URL.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New POST /api/auth/avatar — accepts multipart FormData, uploads image to
R2 under avatars/{userId}/{ts}.ext, saves URL to users.avatar_url
- GET /api/auth/profile now returns avatarUrl field
- /profile page: real avatar display (image or initials fallback), hidden
file input wired to "Change Photo" button, spinner overlay while uploading,
inline success/error message; name save and photo upload are independent
NOTE: This is the parent user avatar (mama/daddy). The baby profile photo
on the homepage card is separate.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Non-today rows: hover:bg-rose-50 / dark:hover:bg-gray-700
- Today (rose) rows: hover:bg-white/20 overlay so highlight still reads on rose bg
- transition-all for smooth fade
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add group + hover:bg-rose-50 + hover:shadow-sm to timeline log rows
- Add group + hover:bg-rose-50 to day-sheet log rows
- Chevron › turns rose-400 on hover (group-hover) in both places
- transition-all for smooth background + shadow animation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
External favicon URL (www.google.com/favicon.ico) fails to load in
production due to CSP/network restrictions. Inline SVG has no external
dependency and renders the correct Google logo at all sizes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Behind Traefik, request.url resolves to http://0.0.0.0:3000/... (the
internal Docker address). Using that as the redirect base sent browsers
to 0.0.0.0, causing ERR_SSL_PROTOCOL_ERROR. Switch all server-side
redirects in the Google callback and verify-email routes to use
NEXT_PUBLIC_APP_URL (with tia.manohargupta.com fallback).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
Users page:
- Delete was silently failing with FK constraint violations — sessions,
accounts, support_tickets and family_members all have ON DELETE no action
refs to users. Now cascades in correct order before deleting the user.
- Added error/success toast notifications so failures are visible
- Delete button shows loading spinner while in-flight; all buttons
disabled during operation to prevent double-submit
- Always optimistically removes row on success (no full refetch needed)
Families page:
- Replaced browser prompt() for "New Family" with inline form — prompt()
is blocked in some environments (CSP, iframes, browser settings)
- Fixed role-before-email bug: role dropdown was silently lost when changed
before typing email, because onChange reset the whole addMember state.
Now uses per-family form state with stable field updates.
- Remove member button shows loading spinner; disabled during operation
- Tier change button shows loading; disabled during other tier changes
- Added error/success toast notifications for all actions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New /admin/activity page: live login events, failed attempts, active
sessions from audit_log + sessions tables; auto-refresh toggle
- New /api/admin/activity route: queries audit_log + sessions for
stats (active sessions, logins/failures 24h, signups 7d) and events
- Fix /api/admin/stats: real growth charts (families/users by day),
real children-by-age, real conversion rate, active sessions count,
and login/failure counts — was all hardcoded empty arrays before
- Fix /api/admin/analytics: avg logs per family now divides by actual
family count instead of hardcoded 1
- Dashboard: 6-card grid adding Active Sessions + Failed Logins 24h
with links to Activity Monitor; bar charts now show hover counts
- Families: inline tier upgrade/downgrade button (Pro ↑ / Free ↓)
wired to existing PATCH API; member panel polished
- Support: admin reply thread using support_responses table; Cmd+Enter
to send; conversation view with original message + admin replies;
auto-moves ticket to in_progress on first reply
- Settings: honest read-only display for env-var-controlled settings
(pricing, AI config); editable free-tier limits that write to DB
- New /api/admin/families/limits route for bulk free-tier limit update
- Sidebar: added Activity Monitor nav item
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Collapsed state now fits entirely in one row:
[avatar] [name] · [truncated text] [tiny thumbnail] [▼]
No second row needed. Expanded state shows the normal two-line
author block (name + time) as before.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Collapsed preview (thumbnail + text) moved to its own row centered
below the author line instead of being pushed to the right
- Chevron ▼/▲ stays on the author row right side as the only element
- Edit post and Delete post now call setCollapsed(false) first so the
edit textarea / delete confirmation is always visible when triggered
from a collapsed card
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Posts start collapsed showing author, timestamp, a thumbnail (if image)
and truncated text preview with ▼ chevron. Tap the author row to expand
the full post (body, full image, reactions, comments). Tap again to
collapse. Lets users scan many posts quickly and expand only what
interests them.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove overflow-hidden from PostCard root so the ⋯ dropdown menu
is no longer clipped by the card boundary
- Add Edit Post option to menu (own posts only) with inline textarea
and Save/Cancel; calls new PATCH /api/circles/[id]/posts/[postId]
- Add PATCH endpoint: author-only text edit
- Fix image display: object-contain (no crop) instead of object-cover
- Add tap-to-fullscreen lightbox: clicking any post image opens a
full-screen black overlay with the image at natural size, ✕ to close
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Direct browser PUT to R2's S3 endpoint is blocked by CORS. Replace the
presigned-URL client-side upload with a server-side upload endpoint:
client sends FormData → server uploads to R2 with PutObjectCommand →
returns tmpKey. No browser-to-R2 connection needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Server: normalise empty/missing MIME type by sniffing file extension so
iOS HEIC/HEIF and camera photos (which send empty type) are accepted
- Server: add image/heif and image/gif to allowed types
- Server: return normalised contentType in presign response
- Client: check presignRes.ok before uploading; use server contentType
for the PUT to R2 so the header matches what was signed
- Client: show error message in modal instead of silent catch
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Admin invites by entering email instead of copying a link
- If email matches existing Tia user → creates pending invite visible
on their Circles page with Accept/Decline buttons
- If email is not registered → sends Resend email with signup link
that lands them directly in the circle after account creation
- DB migration adds invited_email + invited_family_id to circle_invites
- New GET /api/circles/invites endpoint for pending invite banners
- Remove clipboard-copy approach entirely
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Catches errors from the circle_members SELECT query and auth
that were escaping the narrower try-catch and returning empty 500s.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two issues prevented 0003_circles.sql from running:
1. Missing -->statement-breakpoint markers (Drizzle splits SQL by these)
2. migrate.ts used DATABASE_URL (tia_app, no DDL privileges) instead of
DATABASE_URL_SUPERUSER — now prefers superuser URL with fallback to
DATABASE_URL for local dev
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The SQL file existed but was missing from _journal.json so the
migrator skipped it on deploy. Adding the journal entry ensures
the circles tables are created on next container boot.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds full circle functionality — private social groups for trusted families
to share milestones, memories, and posts with reactions and comments.
- 7-table DB migration: circles, members, invites, posts, comments, reactions, reports
- 11 API routes: create/list circles, posts feed, comments, emoji reactions, invite tokens, join flow, member management, reporting
- 3 new pages: /circle (list), /circle/[id] (feed + PostCard + CreatePostModal), /circle/join/[token]
- Copy-on-share for memory photos (independent R2 objects, never references originals)
- Admin controls: invite generation, member promote/demote/remove, last-admin guard
- C9 privacy consent screen before first post
- Menu entry added
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New PATCH /api/children/[id] — updates image_url in children table
- New POST /api/children/[id] — returns presigned R2 URL for profile
photos (stored under profiles/{childId}/ prefix, no memories row)
- FamilyProvider: expose updateChildImage() so UI updates instantly
without a full re-fetch after upload
- Home page baby card: photo avatar is now a separate tap target from
the growth link. Tap photo → file picker → upload to R2 → save URL.
Camera overlay (📷) appears on hover/tap; ⏳ shown while uploading.
Tapping name/age/arrow still navigates to growth as before.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each row in a day chip is now its own button. Tapping 🍼×2 on Thursday
opens a sheet scoped to feeds on Thursday only — not all logs for that day.
Sheet shows entries for that specific type, with edit/delete per entry and
a single focused "+ Add [type]" CTA at the bottom.
Rows showing ×0 render dimmed so missing entries stand out at a glance.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Moved guidelines row ABOVE the 4-day strip (correct order)
- 4-day strip: each chip is now tappable
→ opens a day-detail bottom sheet showing all logs for that day
→ each log row has ‹ › arrow; tap opens edit/delete action sheet
→ empty state tells mama to use Generate sample history to pre-fill
→ quick-add row at bottom (+ Feed / + Sleep / + Diaper) for fast logging
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- FAB raised to bottom-20 to clear fixed bottom nav
- Branded loading: bouncing 🍼😴🚼 emojis
- Back button: white pill with shadow (matches other pages)
- Generate History moved to ⋯ overflow menu (keeps header clean)
- Filter pills: emoji labels (🍼 Feed / 😴 Sleep / 🚼 Diaper) + scrollbar-hide
- Daily summary bar: today's feed/diaper/sleep counts at a glance
- 4-day overview strip: quick multi-day snapshot above timeline
- Collapsible guidelines card: collapsed shows x/target fractions,
expands to progress bars with actual/target display
- Today/Yesterday/weekday labels in timeline; Today styled in rose
- Better empty state with emoji and "Tap + to start logging" CTA
- Tap any log row → action sheet with Edit and Delete
Edit: pre-fills LogModal with same values, deletes old log on save
Delete: inline confirmation (no browser confirm()), then refresh
- New DELETE + PATCH handlers at /api/logs/[id] with family ownership check
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reverted fixed viewport positioning back to absolute — the parent cards
no longer have overflow-hidden so absolute works correctly now. Both
dropdowns (profile share and per-product share) appear directly below/above
their trigger button instead of floating at a random screen corner.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause: parent div had overflow-hidden which clipped absolute-positioned
dropdowns, making them render but be invisible (looked like click did nothing).
- Removed overflow-hidden from profile card container
- Share dropdowns now use fixed positioning to escape any parent overflow
- Merged chevron into the expand button so the whole left area is one tap target
- Per-product share sheet also changed to fixed bottom-28 right-4
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Each product row now has an ↗ share button (product URL shared, not profile)
WhatsApp message: "Found this for our baby — [title]: [url]"
- Only one product share sheet open at a time; closes on backdrop tap
- Profile page share (↗) moved to the profile card header row where it
contextually belongs — shares the /m/slug link, not a product
- Removed the share button that was on the Products section header
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Collapsible profile card:
- Starts collapsed if profile already exists (shows name + URL slug)
- Expands/collapses via chevron toggle row
- Auto-collapses after Save Profile succeeds
Share sheet on Product Recommendations:
- ↗ Share button appears once a valid slug is set
- Options: Copy link (clipboard, shows ✅ feedback), WhatsApp deep link,
and native Web Share API ("More options") when browser supports it
- Backdrop click closes the sheet
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Settings/Sign Out used absolute bottom-0, overlapping the bottom of the
menu items list on shorter screens. Converted to normal flow with mt-4
and added pb-28 so the list clears the bottom nav bar.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>