Commit Graph
78 Commits
Author SHA1 Message Date
adminandClaude Sonnet 4.6 d17d71e55a feat(health): emergency vet with OSM search and map
- EmergencyVet model: name, address, phone, website, lat, lng (1:1 to Pet)
- health router: getEmergencyVet, setEmergencyVet, deleteEmergencyVet
- EmergencyVetSection: Nominatim search (debounced 500ms, OSM attribution),
  result dropdown auto-fills address + coordinates, manual override for all fields
- OSM iframe map (no API key) with "In Karte öffnen" link; shown in edit
  mode as coordinate preview and in view mode after save
- Health card (public): red-accented emergency vet block with phone link,
  website link and OSM map; map hidden on print (print:hidden)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 17:39:46 +02:00
adminandClaude Sonnet 4.6 b0d6a4d7d1 fix(cron): birthday notifications require 1+ year like adoptedAt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 17:14:47 +02:00
adminandClaude Sonnet 4.6 f6aa825ad1 feat(v2): algorithmic feed toggle, anniversary notifications, welcome card
Algorithmic Feed:
- feed.getFeed gains `mode: chrono | algo` param (default: chrono)
- Algo scoring: reactions*3 + comments*5 + reposts*4 + recency bonus
- FeedList: pill toggle Fuer dich (algo) / Aktuell (chrono), saved to localStorage

Anniversary Notifications:
- Pet schema: birthday Date?, adoptedAt Date?
- NotificationType enum: +BIRTHDAY, +ADOPTION_DAY
- /api/cron/anniversaries: daily cron (08:00 UTC via vercel.json)
- Pet edit form: birthday + adoptedAt date pickers
- Notifications page: Cake/Home icons, German copy, links to health page

New-User Welcome Card:
- WelcomeCard shown at top of feed while localStorage flag unset
- 5 feature tiles: Posts/Videos, Stories, Milestones, DMs, Health
- Dismissable via X or Verstanden; per-pet flag in localStorage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 17:10:50 +02:00
adminandClaude Sonnet 4.6 80559c3b3f feat(health): health card share link, PDF export, weight chart in card
- ShareDialog: expiry selector (7/14/30/60/90 days, default 14), shows
  expiry date after generation, "Als PDF öffnen / drucken" button
- WeightChart extracted to standalone component (WeightChart.tsx) so it
  can be shared between HealthDashboard and the public health card page
- Health card page: renders WeightChart when 2+ weight entries exist
- AutoPrint client component: auto-triggers window.print() when card is
  opened with ?print=1 (600ms delay for render to settle)
- README: Phase 6 + Phase 7 marked complete with full feature summaries;
  project structure and router list updated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 06:06:28 +02:00
adminandClaude Sonnet 4.6 f1c798a4b0 feat(07): Phase 7 — Health & Vet Tracking
Schema: WeightLog, VetVisit, Vaccine, HealthCard models
Router: health.ts — CRUD for weight/vet/vaccines + health card token management
Pages:
  - /pets/[petId]/health — owner-only dashboard (weight, vet visits, vaccines)
  - /health-card/[token] — public shareable health card (no auth required)
Profile: Health button added for own pets; Edit + Health side by side

Health data is owner-private; the Health Card link is the only public
access point, shareable via token URL (vet, friends, sitters).
Token can be regenerated to revoke access.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 05:07:56 +02:00
adminandClaude Sonnet 4.6 5d396a7bb4 fix: make pet profiles clickable from feed, search, and notifications
- Add pets.getProfile procedure (no ownership check) so any auth'd user can view any pet profile
- Profile page now uses getProfile; Edit link and owner attribution only shown for own pets
- PostCard header: wrap PawRing + pet name in Link to /pets/[petId]
- Search Pets tab + People tab pet rows: wrap avatar/name in Link to /pets/[petId]

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 04:52:45 +02:00
adminandClaude Sonnet 4.6 e2e932804b feat(06): Phase 6 — Direct Messages
- Schema: DmPolicy enum (EVERYONE/FOLLOWERS_ONLY), dmPolicy on Pet, Conversation + Message models
- conversationsRouter: list (inbox), getOrCreate (with DmPolicy + block check), unreadCount
- messagesRouter: list (3s poll, auto mark-read), send (+ MESSAGE notification), markRead
- /messages inbox page with unread badges + last message preview
- /messages/[conversationId] thread with bubble UI, day dividers, Enter to send
- Message button on pet profiles (ProfileActions) → getOrCreate → navigate to thread
- Sidebar: Messages link activated
- Pet edit page: DM Privacy setting (Everyone / Only pets I follow)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 09:45:45 +02:00
adminandClaude Sonnet 4.6 da0f9f2bb7 fix: resolve 5 bugs from dev testing
- PostCard: add variant="detail" (object-contain + black bg) for profile sheet — full image in original format
- VideoCard: getByPostId polls Mux API directly when PROCESSING, self-heals without webhooks in dev
- PawRing: gray ring for all-seen stories, orange for unseen; reads viewerPetId from ActivePetContext
- Search: add People tab via new search.byOwner (Clerk username search → owner's pets)
- Notifications: rows now navigate to actor profile (FOLLOW) or pet profile (REACTION/COMMENT)
- Notifications router: include post.petId for navigation target

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 08:41:46 +02:00
adminandClaude Sonnet 4.6 60366691e8 feat(05): Phase 5 — short-form video via Mux
Schema:
- Add VideoPost model (muxUploadId, muxAssetId, muxPlaybackId,
  status, durationSecs, aspectRatio) linked 1-to-1 with Post
- Add VideoStatus enum (PROCESSING/READY/ERROR)
- Add PostType.VIDEO

Backend:
- src/lib/mux.ts — singleton Mux client + webhook secret
- videos router: createUpload (Mux direct upload URL + PROCESSING post),
  getByPostId (status polling), updateCaption
- POST /api/webhooks/mux: verifies signature; handles
  video.upload.asset_created (store muxAssetId),
  video.asset.ready (set READY + playbackId + fan-out to feed),
  video.asset.errored (set ERROR)
- feed.ts + posts.byPetId: include videoPost in all post queries

UI:
- VideoUploadForm: drop zone → XHR PUT to Mux upload URL with
  progress bar; caption field; transitions to "Processing…" on success
- VideoCard: PROCESSING shows spinner; READY renders MuxPlayer
  (HLS, orange accent); polls every 5s until status changes
- PostTypeSheet: "Video Post" option added (between Photo and Milestone)
- PostCard: VIDEO type renders VideoCard instead of image carousel

Env vars: MUX_TOKEN_ID, MUX_TOKEN_SECRET, MUX_WEBHOOK_SECRET,
NEXT_PUBLIC_APP_URL — placeholders added to .env.local

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 13:27:43 +02:00
adminandClaude Sonnet 4.6 08fd0dfb71 feat(04): Phase 4 — in-app notifications
Schema:
- Add NotificationType enum (FOLLOW, REACTION, COMMENT, MESSAGE)
- Add Notification model with recipientPet, actorPet, post, comment
  relations; index on [recipientPetId, read, createdAt desc]

Backend:
- notifications router: list (cursor-paginated), unreadCount,
  markRead, markAllRead
- follows.create: fire-and-forget FOLLOW notification to followee
- reactions.toggle: REACTION notification on create (skip self)
- comments.create: COMMENT notification with commentId (skip self)
  All triggers use .catch(()=>{}) to never fail the main action

UI:
- NotificationBell component with orange badge, polls every 30s
- /notifications page: list with avatar, type icon, text, thumbnail,
  read/unread dot; auto-marks all read on visit
- Sidebar: Notifications link activated with NotificationBell
- MobileNav: Bell replaces Search tab (Search still at /search)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 12:59:23 +02:00
adminandClaude Sonnet 4.6 01b4f18a97 fix: wrap useSearchParams in Suspense on reset-password page
Next.js static rendering requires useSearchParams() to be inside a
Suspense boundary. Splits page into ResetPasswordContent (inner) +
ResetPasswordPage (Suspense wrapper). Caught by npm run build.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 12:21:07 +02:00
adminandClaude Sonnet 4.6 847ac97bde feat(03-06): Hashtag search end-to-end — Phase 3 complete
- hashtag-helpers.ts: extractHashtags (regex+dedup) + saveHashtags (upsert)
- parse-caption.ts: parseCaption → CaptionSegment[] for client rendering
- search.byHashtag GREEN: hashtag.findUnique → postHashtag.findMany + pagination
- posts.create + reposts.create: extract+save hashtags after post creation
- /hashtag/[tag]: infinite scroll post grid + useInView sentinel
- /search Tags tab: byHashtag query wired, 3-col post grid
- PostCard: parseCaption replaces raw caption — hashtags are orange Link to /hashtag/[tag]
- STATE.md: Phase 3 complete, 3/7 phases done

Tests: 67 passing, 0 failing (5 todo), tsc clean

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 12:16:03 +02:00
adminandClaude Sonnet 4.6 59a3be27b0 feat(03-05): Explore + pet search — routers GREEN + pages + nav
- explore.listPets: publicProcedure, speciesId/breedId filter, cursor pagination
- search.pets: protectedProcedure, ILIKE name+breed OR, cursor pagination
- ExploreCard: avatar, breed badge, follower count, FollowButton
- /explore: species tabs + breed filter Select + infinite pet grid
- /search: debounced input, Pets tab with FollowButton rows, Tags stub
- Sidebar: Explore activated, Search added
- MobileNav: Explore activated, Notifications placeholder → Search

Tests: explore 3/3 GREEN, search.pets 2/2 GREEN; 65 total passing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 12:09:24 +02:00
admin 3fa62d2ce0 feat(02-06): MilestoneForm + PostTypeSheet wiring + ProfileTabs + profile page — Phase 2 complete 2026-06-13 10:22:35 +02:00
admin 9a901e3485 feat(02-05): wire seams — PawRing in PostCard, StoryTray in feed, StoryForm in PostTypeSheet 2026-06-13 10:11:09 +02:00
admin 0428cf2a04 feat(02-02): social + safety UI — FollowButton, PetListItem, BlockDialog, ReportSheet, followers/following pages
- FollowButton: optimistic follow/unfollow, revert on error, AlertDialog unfollow confirm (SOCL-01/02)
- PetListItem: avatar + name + species badge + right-aligned FollowButton (SOCL-03)
- BlockDialog: AlertDialog with exact UI-SPEC copy, destructive Block CTA (SAFE-01)
- ReportSheet: 5 RadioGroup reasons, notes Textarea, destructive Submit CTA (SAFE-02)
- followers/page.tsx: RSC shell "{N} Followers" heading + FollowersList client (SOCL-03)
- following/page.tsx: RSC shell "Following {N}" heading + FollowingList client (SOCL-03)
- pets/[petId]/page.tsx: stats row (posts/followers/following) + ProfileActions (SOCL-03)
- ProfileActions: FollowButton + MoreHorizontal options menu + BlockDialog + ReportSheet
- Fix: test prisma cast to `as any` resolving TS2322 type errors (Rule 1 - Bug)
- tsc exits 0; vitest 36 passed
2026-06-07 12:37:45 +02:00
admin e9b286b4df feat(02-03): /feed page + home redirect + nav Home→/feed
- src/app/(app)/feed/page.tsx: RSC shell with StoryTray seam + FeedList, metadata "Home — PawFeed"
- src/app/page.tsx: redirect("/feed") per D-04 (home route → /feed after Phase 2)
- Sidebar.tsx: Home href "/" → "/feed"; active detection handles /feed and / (D-05)
- MobileNav.tsx: Home href "/" → "/feed"; active state updated for /feed (D-05)
- (app)/layout.tsx: unchanged — zero-pets redirect to /onboarding/pet preserved (D-06)
2026-06-07 12:37:04 +02:00
admin eeab28e8cb chore: rename project from OnlyPets to PawFeed (domain: pawfeed.org) 2026-06-06 11:51:45 +02:00
admin 95e42f5474 fix(01-04): replace all R2/api-avatar URLs with Supabase Storage CDN via getAvatarUrl() 2026-06-06 11:38:37 +02:00
admin fb8bb5c8fe feat(01-04): wire CDN avatar into profile, edit page, sidebar, and mobile nav
- pets/[petId]/page.tsx: replace /api/avatar stub with NEXT_PUBLIC_R2_PUBLIC_URL CDN URL
- pets/[petId]/edit/page.tsx: pass initialAvatarKey to PetForm so AvatarUpload shows current photo
- Sidebar.tsx: add avatarKey to Pet type; show AvatarImage from CDN for active pet and dropdown list
- MobileNav.tsx: same CDN avatar pattern for mobile pet switcher
- next.config.ts: add images.remotePatterns for R2 CDN hostname (T-4-04 safeguard)
2026-06-06 11:10:28 +02:00
admin 3d0e2e6961 fix(01-03): pet card click navigates to profile page 2026-06-06 10:49:13 +02:00
admin 95dfec7bdf feat(01-03): add pet profile pages (My Pets grid, profile view, edit, onboarding)
- My Pets: server-fetched list + PetGrid client island for active pet selection
- Pet profile view: avatar/name/species badge, "Managed by @owner" subordination
- Edit pet: PetForm in edit mode + delete confirmation dialog (UI-SPEC copy)
- Delete dialog: "Delete pet profile?", "Yes, delete", "Keep pet" per UI-SPEC
- Onboarding page: replaced stub with PetForm mode=create, redirects to /pets/[petId]
- Used buttonVariants for Link-as-button (base-nova lacks asChild)
2026-06-06 10:17:34 +02:00
admin 32226739b7 fix(01-02): move onboarding/pet outside (app) group — breaks redirect loop 2026-06-06 09:59:01 +02:00
admin 177403276b fix(01-02): update layout comment to reference middleware.ts 2026-06-06 09:43:00 +02:00
admin e7d300f9c2 feat(01-02): protected app shell with sidebar, mobile nav, onboarding redirect
- (app)/layout.tsx: server component with auth() check + pets.list + redirect to /onboarding/pet if no pets
- Sidebar.tsx: 240px desktop nav (lg:), pet switcher with active state, nav links with orange-500 active indicator
- MobileNav.tsx: fixed bottom bar (h-16, lg:hidden), pet switcher above tabs, orange-500 center post button
- onboarding/pet/page.tsx: stub with Plan 03 placeholder (replaces walking skeleton client form)
- Deviation: base-nova shadcn uses @base-ui/react/menu (no asChild support) — used onClick+useRouter
- Deviation: Clerk 7 UserButton no longer accepts afterSignOutUrl
2026-06-06 08:57:38 +02:00
admin 325eaa9a25 feat(01-02): auth layout and Clerk-rendered auth pages
- (auth)/layout.tsx: centered 448px container, items-center, bg-background
- sign-up/[[...sign-up]]/page.tsx: Clerk <SignUp> with orange-500 appearance, redirects to /onboarding/pet
- log-in/[[...sign-in]]/page.tsx: Clerk <SignIn> with orange-500 appearance, redirects to /pets
- forgot-password/page.tsx: two-step Clerk 7 reset flow (create identifier + resetPasswordEmailCode.sendCode)
- reset-password/page.tsx: Clerk 7 resetPasswordEmailCode.verifyCode + submitPassword + finalize
- verify-email/page.tsx: static server component + ResendEmailButton client island
- Deviation: Clerk 7 SignInFutureResource API used (no isLoaded/setActive/attemptFirstFactor)
2026-06-06 08:52:23 +02:00
admin e0214ea0f8 feat(01-01): wire Clerk proxy.ts, Prisma 7 schema + singleton, tRPC stack, R2 client, ActivePetContext
- Create proxy.ts at repo root with clerkMiddleware + createRouteMatcher (public allowlist: sign-up, log-in, forgot-password, reset-password, verify-email)
- Create prisma/schema.prisma: Owner, Species, Breed, Pet models with ownership index and FK constraints
- Create prisma.config.ts with Prisma 7 defineConfig (datasource url via env)
- Create src/lib/prisma.ts: Prisma 7 singleton using @prisma/adapter-pg for local dev, withAccelerate() for Prisma Accelerate on Vercel
- Create src/lib/r2.ts (server-only): S3Client + getPresignedUploadUrl with contentType enforcement and 300s expiry
- Create src/lib/auth.ts: getOwnerId() Clerk auth wrapper
- Create src/trpc/init.ts: createTRPCContext, router, publicProcedure, protectedProcedure (throws UNAUTHORIZED)
- Create src/trpc/routers/pets.ts: pets.create (upserts Owner, creates Pet with ownerId=ctx.userId), pets.list (scoped), pets.byId (ownership-asserted)
- Create src/trpc/routers/_app.ts: appRouter + AppRouter type export
- Create src/trpc/query-client.ts, server.ts (RSC caller), client.tsx (TRPCReactProvider + useTRPC)
- Create src/app/api/trpc/[trpc]/route.ts: tRPC fetch handler (GET + POST)
- Create src/context/ActivePetContext.tsx + src/hooks/useActivePet.ts: localStorage-backed active pet context
- Create prisma/seed.ts: seeds Dog/Cat/Bird species idempotently via upsert
- Update src/app/layout.tsx: wrap children in ClerkProvider (orange primary), TRPCReactProvider, ActivePetProvider, Toaster
- Install @prisma/adapter-pg + pg for Prisma 7 local dev (Rule 2: required for Prisma 7 engine compatibility)
- [Rule 1 - Bug] Prisma 7 no longer supports url/directUrl in schema.prisma; moved to prisma.config.ts with defineConfig
2026-06-05 19:18:16 +02:00
admin dbc074e9ee chore(01-01): scaffold Next.js 16 + install full stack + Vitest test scaffold
- Create-next-app Next.js 16.2.7 with TypeScript 6.0.3, React 19, Tailwind v4, Turbopack
- Install all pinned runtime deps: Clerk 7.4.3, tRPC 11.17.0, Prisma 7.8.0, Zod 4.4.3, AWS SDK 3.1062.0, Sonner 2.0.7, Upstash Redis 1.38.0
- Configure Tailwind v4 (postcss.config.mjs uses @tailwindcss/postcss; globals.css uses @import "tailwindcss")
- Initialize shadcn/ui with default preset; add all Phase 1 components (button, input, form, label, textarea, select, avatar, card, dialog, dropdown-menu, sonner, progress, separator, badge, skeleton)
- Set orange-500 design system: --primary: oklch(0.7024 0.1967 41.45); --background: oklch(0.9882 0 0)
- Load Inter via next/font/google in root layout
- Create vitest.config.ts with jsdom environment and @ path alias
- Create Wave 0 test scaffolds (22 it.todo tests): auth, pet-profile, media-upload + prisma-mock helper
- Create .env.example listing all required service env vars
2026-06-05 19:10:46 +02:00