Commit Graph
89 Commits
Author SHA1 Message Date
admin 9f9db9e742 feat(landing): interactive redesign with scroll reveals and parallax
Rebuilds the public landing page per the structural reference in
Gitea #11 (navbar layout, rounded hero card with phone mockup +
floating badges, catchphrase section, second detail-feature card,
footer repeating the navbar) — colors/typography adapted to PawFeed's
existing orange brand instead of the generic SaaS reference palette.

New:
- Sticky navbar (logo, Home/About-coming-soon/Impressum, language
  switcher, Login + Sign-up pill)
- Hero: rounded-4xl gradient card, phone-framed app preview with
  three floating badge cards (idle bob animation), scroll-linked
  parallax on the background paw watermark via native CSS
  animation-timeline: view() (progressive enhancement, no JS)
- Catchphrase section reusing the existing 4 feature translations,
  each card staggered into view via a new ScrollReveal client
  component (IntersectionObserver, no scroll-handler churn)
- Second rounded detail-feature card with two checklist points
- Footer repeats the navbar links + tagline + "designed by" line

All reveal/parallax/float animations respect prefers-reduced-motion
and are pure CSS — no new animation dependency. Verified in a real
browser at 390px and ~1568px (mobile nav collapse, hero stacking,
floating badges hidden on mobile, scroll reveals firing correctly).

Gitea #11
2026-08-12 14:04:57 +02:00
admin 6fd5f2dff8 feat(pet): circular avatar cropping with zoom before upload
Adds an AvatarCropDialog step (react-easy-crop, cropShape="round")
between file selection and the presigned-URL upload in AvatarUpload:
the user crops/zooms a circular region, which is re-encoded via canvas
to the original file's mime type before the existing upload flow runs.
File-size/type validation moved from uploadFile into the new
selectFile gate that opens the dialog.

Gitea #13
2026-08-12 12:52:32 +02:00
admin 5fbf39593d feat(ui): initials-color avatars and collapsible desktop sidebar
Adds a PetAvatar wrapper (hash(name) mod 360 -> HSL background) and
migrates the four most-visible avatar call sites (sidebar, mobile nav,
post card, post detail incl. comments) to use it instead of duplicated
inline initials logic.

Desktop sidebar can now collapse to an icon-only 76px rail, persisted
in localStorage (same client-init pattern as ActivePetContext). All
nav rows, the new-post button, and the owner row adapt; secondary
sections (language switcher, legal footer) hide when collapsed.

Gitea #12, #14
2026-08-12 12:52:21 +02:00
admin e39b3f1f6e feat(feed): spring/fill/burst animation on paw reactions
Adds a CSS-only spring-scale + expanding-burst-overlay animation
triggered when a pet paw-reacts to a post, respecting
prefers-reduced-motion. Only plays on the like transition, not unlike.

Gitea #15
2026-08-12 12:52:07 +02:00
admin 3a5b72b0f7 fix(verification): profile context in review, i18n on edit page, badge in pet switcher
Three follow-ups from testing the verification flow:
- listVerificationRequests now pulls bio, join date, post/follower
  counts, and the owner's ban+warning status onto the review card, so a
  moderator can check the checklist without leaving the page
- Edit-pet page was entirely hardcoded English regardless of locale —
  converted to next-intl (new EditPet namespace, en+de)
- the pet switcher (Sidebar + MobileNav) didn't show the verified badge,
  only the full profile page did — added it to both
2026-08-12 10:19:16 +02:00
admin 0da56a2aef fix(landing): replace flag emoji with SVG chips, tighten app-preview crop
Regional-indicator flag emoji render as bare "GB"/"DE" text on stock
Windows (no color-flag font substitution), which defeated the point
of switching from text labels to flags — swaps in small inline SVG
flag components instead, guaranteed to render everywhere.

Also re-crops the app-preview screenshot: the original had a wide
empty gap between the sidebar and the feed column baked into the
capture (an artifact of the live app's centered-column layout at that
viewport width). Stitches the sidebar and feed regions together
tightly via sharp instead.
2026-08-11 20:03:18 +02:00
admin 1eb27dfeb3 feat(landing): add header, app preview, feature overview, and footer
The new public landing page (/) was hero-only — missing a language
switcher, legal links, and anything showing what the product actually
looks like. Adds:
- A slim header with the wordmark and a flag-based language switcher
  (LanguageSwitcher now shows 🇬🇧/🇩🇪 instead of English/Deutsch text,
  used here and everywhere else it's already mounted)
- An app-preview section: a real screenshot of the live feed inside a
  browser-chrome mockup card
- A 4-item feature overview (pet-first profiles, posts/stories/video,
  health tracking, pet-to-pet messaging)
- A footer with Impressum/Datenschutz/AGB links, matching what the
  authenticated sidebar already links to
2026-08-11 19:58:59 +02:00
admin efdf407642 feat(admin): add species/breed ad targeting (Gitea #9)
Advertisement.species existed in the schema but was never wired up —
the admin form never let you set it and the feed's ad query ignored it
entirely, showing every active ad to every pet regardless of species.

Adds a new Advertisement.breeds field alongside it and wires both
through the whole path: a targeting UI in the ad create/edit dialog
(species checkboxes, each expandable into its own breed checklist),
and a filter in admin.getActiveAds (now scoped to the viewing pet) so
cat food ads stop reaching dog owners. Empty species/breeds = shown to
everyone, unchanged from today's behavior.
2026-08-11 15:09:56 +02:00
admin b6610ca7a7 feat(media): add Blurhash placeholders for images across feed, stories, and admin views
Gitea #1: prevents blank image flashes while photos load. Blurhash is
computed client-side at upload time (canvas downsampling + the
blurhash package) for posts, milestones, and stories, stored alongside
each PostImage/Story row, and decoded into a smooth color placeholder
via the new BlurImage component that cross-fades to the loaded photo.
Wired into every content-photo surface: feed cards, post detail zoom,
reposts, milestone cards, story viewer, explore/search grids,
notification thumbnails, profile grid, and the admin posts/reports
panels. Pet avatars and ad creatives intentionally excluded — separate
content pipelines with disproportionate effort for the payoff.
2026-08-11 10:03:08 +02:00
admin 17f560eb46 feat(legal): add mandatory upload-rights and data-processing consent to onboarding
Gitea #3: harden Datenschutz for the now-public site. Adds a blocking
first-pet-onboarding step (two checkboxes) confirming the owner holds
rights to uploaded photos/videos and consents to data transfer to
Clerk/Cloudflare/Mux/Supabase. Consent timestamps are stored separately
on Owner so either declaration can be re-requested independently later.
Also documents GlitchTip as a processor and adds an explicit consent
section to the Datenschutzerklärung.
2026-08-11 09:33:39 +02:00
admin ec9573f33a feat(pet-profile): make the profile avatar clickable to zoom
User feedback: pet profile pictures couldn't be enlarged. New
PetAvatarZoom client component wraps the profile avatar in a button —
clicking opens a lightbox Dialog showing the full photo, reusing
DialogContent's existing zoom-in-95 + fade entrance animation rather
than hand-rolling a new transition. A subtle hover scale + cursor-zoom-in
signals it's interactive. No-op when the pet has no avatar set (falls
back to initials, nothing to zoom into).
2026-07-23 20:32:00 +02:00
admin b4ef5bc195 feat(ads): track and display per-ad view counts for advertisers
Advertisers/admins had no way to see how many times an ad was actually
shown — only engagement counts (reactions/comments/reposts) existed.
Adds Advertisement.adViewCount, a denormalized counter matching the
existing engagement-counter pattern.

AdCard.tsx fires ads.recordView once per card, the first time it
scrolls ≥50% into view (react-intersection-observer's triggerOnce,
already a project dependency via FeedList's pagination sentinel) —
matches standard "viewable impression" semantics rather than counting
on mount (which would also count ads that render off-screen and are
never actually seen). Not deduplicated per pet, unlike reactions/reposts
— an impression counts every time an ad becomes visible, matching how
ad platforms report view counts.

Admin ads panel (/p/[secret]/ads) now shows view/reaction/comment/
repost counts inline on every ad row — the actual "results" advertisers
want from a placement, previously not surfaced anywhere despite already
existing on the Advertisement row for the other three counters.
2026-07-23 20:22:42 +02:00
admin 0e02d3da71 feat(admin): add posts-over-time and owner-growth charts to the dashboard
Two new chart panels on the admin dashboard, each with a day/week/month
granularity toggle:
- "Posts over time" — bar chart of new posts per bucket (trailing 30
  days / 12 weeks / 12 months, zero-filled so gaps show as zero, not
  missing points).
- "Owner growth" — line+area chart of the cumulative total registered
  owners, seeded from a pre-window baseline count so the curve starts
  at the correct absolute total instead of zero.

Backend: admin.getPostsOverTime/getOwnerGrowth bucket timestamps in
plain JS (UTC-consistent day/ISO-week-Monday/month-1st truncation) —
deliberately not raw SQL date_trunc, to avoid DB-session-timezone
ambiguity and keep this testable without hitting Postgres. Data volume
is small enough (Alpha-stage app) that fetching bare createdAt columns
and bucketing in memory is the simpler, lower-risk choice over adding
the codebase's first raw-SQL query.

Frontend: new src/components/admin/ (TimeSeriesChart, GranularityToggle)
following the dataviz skill — thin 2px marks, rounded bar corners,
recessive gridlines, hover crosshair+tooltip on both variants, single
brand-orange hue since each chart is one series (no legend needed).
Admin surface is fixed dark (zinc-950), so colors are hardcoded rather
than light/dark-aware, unlike the app-wide WeightChart this borrows its
SVG approach from.

Added 2 regression tests covering zero-fill bucketing and the
cumulative-with-baseline math. prisma-mock.ts gained owner.findMany/
owner.count (previously missing).
2026-07-23 19:59:36 +02:00
admin bb1515cb93 feat(onboarding): add community-guidelines acknowledgment step before pet creation
New users landed directly on pet creation with no prompt to acknowledge
community guidelines — the existing LegalUpdateBanner/LegalDocumentVersion
mechanism only ever ran *after* the first pet existed (since the Owner
row wasn't created until pets.create's owner.upsert), and turned out to
be entirely dormant (zero LegalDocumentVersion rows had ever been
published in prod).

Adds a step 0 to /onboarding/pet: OnboardingGuidelines queries the same
legal.getPendingUpdate the in-app update banner uses and shows the
latest published summary before letting the user continue to pet
creation. Reuses the existing LegalDocumentVersion/admin-publish
infrastructure (/p/[secret]/legal) instead of hardcoding copy, so
content stays editable without a deploy. If nothing has been published
yet, the step is skipped automatically — no dead-end for a fresh
deployment.

legal.acknowledge switched from owner.update to owner.upsert since it
can now be called before the Owner row exists (pre-first-pet).

Deliberately did NOT add a cookie-consent checkbox: Datenschutz §5
already discloses that only strictly-necessary session cookies are used
and no tracking/ad cookies are set without explicit consent — under
GDPR/ePrivacy, strictly-necessary cookies don't require active consent,
only disclosure. The guidelines step links to both Nutzungsbedingungen
and Datenschutz instead of a redundant separate consent mechanism.

Side effect (intentional): existing owners who never explicitly
acknowledged anything will see the same content once via the in-app
LegalUpdateBanner the next time a version is published, since it's the
same underlying data model.
2026-07-23 19:37:40 +02:00
admin ff607a604d feat(feed): cold-start discovery feed for new pets with no follows
A brand-new pet with zero posts of its own and no followees with posts
saw a completely empty feed on first visit. Instead of that dead-feeling
first impression, getFeed now falls back to recent posts from other pets
of the same species, ranked by the existing algoScore (engagement +
recency) — same idea as a "For You" feed, scoped by species instead of
follows.

Only kicks in on the first page (no cursor) of the primary Redis-backed
path, so paginating an already-empty follow feed doesn't keep serving
discovery pages. Response carries a `source: "following" | "discovery"`
flag; FeedList shows a small dismissless banner explaining the posts
aren't from follows yet, otherwise the feed renders identically
(WelcomeCard, feed-mode toggle, PostCard list all unchanged).

Added 2 regression tests covering the discovery-fallback trigger and
the "don't discovery-fallback while paginating" edge case.
2026-07-23 18:21:03 +02:00
admin 18555341e8 fix(explore): let breed-name Badge grow to fit wrapped two-line labels
ExploreCard already set whitespace-normal/break-words on the breed
Badge so long names wrap onto a second line, but Badge's base variant
locks height to h-5 with overflow-hidden (it's designed as a single-line
pill everywhere else it's used) — the pill's border never grew with the
wrapped text, clipping the second line. Overridden locally to h-auto/
overflow-visible (min-h-5 keeps the usual pill height for short names)
instead of touching the shared Badge component used across the app.
2026-07-23 15:27:39 +02:00
admin b7719397e6 feat(i18n): translate pet-profile area (Edit Profile, Health, stats, dropdowns, ReportSheet, BlockDialog)
The entire /pets area (My Pets grid, pet profile page, followers/following
lists, the profile options dropdown, ReportSheet, BlockDialog) had zero
i18n despite ProfileTabs right next to it being fully translated — user
reported still seeing English after switching the app to German.

Adds 7 new message namespaces (PetsPage, PetProfile, FollowersPage,
FollowingPage, ProfileActions, ReportSheet, BlockDialog) with full EN/DE
parity, plus bio/adoptionStory placeholder keys to the existing PetForm
namespace (two placeholders were hardcoded English template literals
despite the rest of that form already being translated).

Server Components (pets/page.tsx, pets/[petId]/page.tsx,
followers/following pages) use getTranslations from next-intl/server
instead of the useTranslations client hook.
2026-07-23 15:02:37 +02:00
admin 4ba6bbc0c3 feat(i18n): translate remaining hardcoded strings (audit finding #14)
Adds 6 new message namespaces (DeleteAccount, NotificationsPage,
PhotoPostForm, StoryForm, MilestoneForm, VideoUploadForm, StoryViewer,
StoryTray, RepostCard) plus a Health.share sub-namespace, with full
EN/DE parity (verified programmatically, 0 keys missing on either side):

- DeleteAccountDialog.tsx: was 100% hardcoded German for a destructive,
  irreversible action — now fully translated.
- notifications/page.tsx: mixed hardcoded English (FOLLOW/REACTION/...)
  with hardcoded German (BIRTHDAY/ADOPTION_DAY) in the same list.
- HealthDashboard.tsx + WeightChart.tsx: date formatting was hardcoded
  to toLocaleDateString("de-DE", ...) regardless of locale, unlike the
  DateWheelPicker in the same forms which correctly used useLocale().
  ShareDialog was entirely untranslated German alongside fully-translated
  sibling tabs.
- PhotoPostForm/MilestoneForm/StoryForm/VideoUploadForm: the entire
  post-creation flow had zero i18n despite the parent PostTypeSheet
  being fully translated.
- StoryViewer/StoryTray/RepostCard: no i18n at all.

health-card/[token]/page.tsx passes a fixed "de-DE" locale to the now
locale-aware WeightChart, since that whole page is a separate,
out-of-audit-scope public page that's intentionally all-German.
2026-07-22 19:55:01 +02:00
admin 4fcde7eb2a fix: remaining MEDIUM/LOW findings from 2026-07-21 audit rerun
- consume-invite: check-then-act race on single-use invite redemption —
  two concurrent redemptions could both pass the pre-check. Now guarded
  with an atomic updateMany(where: {code, usedById: null, revoked: false}).
- schema.prisma: added missing index on Report.targetPostId (used by
  admin.ts's groupBy/filter).
- RepostCard.tsx: reposter link used a raw <a> (full page reload) instead
  of next/link; the original poster's header had no link at all.
- Sidebar.tsx/MobileNav.tsx: removed the duplicated "set active pet on
  first load" effect — ActivePetInitializer already does this centrally
  and is mounted alongside both in the app layout.
- prisma.ts: removed the dead Vercel/Prisma Accelerate code path
  (PRISMA_ACCELERATE_URL is never set — this is a self-hosted Docker
  deployment) and the now-unused @prisma/extension-accelerate dependency.
- .env.example: corrected the NEXT_PUBLIC_APP_URL comment (Mux cors_origin
  is intentionally "*", not wired to it) and documented CRON_SECRET,
  which was missing despite both cron routes requiring it.
- reactions.ts: removed hasReacted/getCount — dead code with no frontend
  callers; getCount also duplicated the denormalized Post.reactionCount.
2026-07-22 19:34:18 +02:00
admin bf49024ace fix: 6 HIGH-severity findings from 2026-07-21 audit rerun
- feed.ts: Postgres fallback path (used when Redis is down) never
  filtered blocked pets, unlike the primary Redis path — blocked pets'
  posts could reappear during a Redis hiccup.
- feed-helpers.ts/feed.ts: getFeedPage always paginated by a hardcoded
  PAGE_SIZE=20, silently ignoring the client's requested (zod-validated
  1-50) limit whenever Redis was up.
- milestones.ts: create accepted any MilestoneType including the
  follower-threshold values (FOLLOWERS_500..FOLLOWERS_1M), which are
  meant to be exclusively auto-awarded — restriction existed only in
  the MilestoneForm picker, not server-side. Now validated against the
  existing USER_INITIATED_MILESTONE_TYPES constant.
- admin.ts: deletePost/deleteReportedPost didn't decrement the original
  post's repostCount when the deleted post was itself a repost, unlike
  reposts.ts's user-facing delete — counter drifted upward permanently.
- PawButton.tsx: count was optimistically guessed off a `reacted` flag
  that can be stale (callers pass initialReacted={false} unconditionally),
  permanently corrupting the displayed count on click. Count is now
  computed from the server's actual toggle result instead of guessed.
- PostDetailDialog.tsx: Report/Block dropdown items had no onClick and
  neither ReportSheet nor BlockDialog were rendered — wired up to match
  PostCard.tsx's existing pattern.
- VideoCard.tsx: invalidated the entire query cache on every video-ready
  transition; scoped to feed/posts/explore router queries instead.
2026-07-22 19:24:21 +02:00
admin 03abe472da fix(security): 4 CRITICAL authorization bugs from 2026-07-21 audit rerun
- health.ts: deleteWeightLog/deleteVetVisit/deleteVaccine checked pet
  ownership but deleted the target row by id alone, never verifying it
  belonged to that pet — any owner could delete another pet's health
  records. Fixed with fetch-then-check (same pattern as comments.ts).
- notifications.ts: list/unreadCount/markAllRead/markRead had zero
  assertPetOwnership calls — any owner could read or clear another
  pet's notification inbox by supplying its (publicly-visible) petId.
- ads.ts: toggleReaction/addComment/toggleRepost never verified petId
  ownership, unlike the near-identical post reactions/comments/reposts
  routers — allowed attributing ad interactions to arbitrary pets.
- FollowButton.tsx: early return before 3 hooks violated Rules of
  Hooks, crashing React when the active pet switched to one already
  rendered in the same list/grid (explore, followers list).

Added regression tests for all 4 (health.ts and notifications.ts had
zero test coverage before this — not a coincidence, per the audit).
2026-07-22 18:21:39 +02:00
admin b3fa42b14a docs(env): document Mux vars in .env.example, fix stale R2 references in AvatarUpload
.env.example was missing MUX_TOKEN_ID/MUX_TOKEN_SECRET/MUX_WEBHOOK_SECRET/
NEXT_PUBLIC_APP_URL even though they're required fields per docker/.env.
AvatarUpload's docblock still described the pre-migration R2 upload path;
storage has been Supabase-only since the R2 removal.

Local .env/.env.local consolidation (not tracked by git) done alongside
this: merged the split DB/Supabase/Clerk/Mux config into .env.local and
removed the dead, never-filled R2 placeholder vars from .env.
2026-07-21 21:24:05 +02:00
admin 3db0692910 refactor(cluster-d): merge getAvatarUrl/getMediaUrl, extract shared upload-progress XHR helper
getAvatarUrl and getMediaUrl were byte-identical bucket URL builders in
two separate files. getAvatarUrl is now a re-export of getMediaUrl —
all ~34 call sites keep working unchanged.

AvatarUpload, MultiImageUpload, and VideoUploadForm each duplicated the
same XHR PUT-with-progress Promise wrapper. The three flows differ too
much (single vs. multi-file, with/without a confirm step) for a single
useFileUpload hook to be a clean fit, so only the truly identical piece
— the XHR PUT itself — was extracted into uploadFileWithProgress().
2026-07-21 20:56:15 +02:00
adminandClaude Sonnet 5 45af6b2a93 perf(cluster-g): fix broken search debounce, memoize feed rendering, lazy-load MuxPlayer
- search/page.tsx: debounce timer lived in the change handler, whose
  cleanup return value is discarded (only useEffect cleanup runs) — every
  keystroke fired an uncancelled query. Extracted shared useDebounce hook,
  applied to both search and mention-textarea (previously undebounced).
- VideoCard: MuxPlayer (pulls in the HLS.js runtime) is now next/dynamic
  instead of a static import, keeping it out of the main feed bundle.
- FeedList: post/ad interleaving array was rebuilt on every render
  (e.g. every scroll-sentinel inView toggle) — now memoized.
- PostCard: memoized now that FeedList hands it referentially stable
  post objects.
- AdCard: replaced a setState-in-effect (server->local sync of
  optimistic reaction/repost state) with the React-recommended
  render-time adjustment pattern; fixed a real react-hooks/set-state-in-effect
  lint error. Two pre-existing, legitimate instances of the same pattern
  (FeedList's hydration-safe localStorage read, PostCard's embla carousel
  subscription) got scoped eslint-disable comments with rationale instead
  of a risky rewrite.
- Reviewed MultiImageUpload.tsx's flagged effect: notifies the parent
  deliberately outside the render phase (already commented, no lint
  violation) — left unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-16 20:16:20 +02:00
adminandClaude Sonnet 5 70115de483 feat(legal): admin-published legal updates + owner acknowledgement banner
Adds a pull-based legal-update flow instead of fanning out through the
petId-scoped Notification model (ToS/Datenschutz acceptance is an
Owner-level concern, not a per-pet one):

- Prisma: LegalDocType enum, LegalDocumentVersion model,
  Owner.legalAcknowledgedAt.
- trpc/routers/legal.ts: admin publish/listVersions, owner-facing
  getPendingUpdate/acknowledge. assertAdmin exported from admin.ts
  instead of duplicated.
- Admin panel /legal: publish form (doc type + change summary) + history.
  Publishing IS sending — every owner sees it on next load until
  acknowledged; republishing re-surfaces it for everyone.
- LegalUpdateBanner mounted in (app)/layout.tsx: non-dismissable sheet
  with the stored change summary, "Verstanden" (acknowledge) and
  "Konto löschen" (routes into DeleteAccountDialog via new
  ?deleteAccount=1 auto-open support) actions.

Closes the last DEBUG-List.md item.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-16 20:08:49 +02:00
adminandClaude Sonnet 5 f14af09b58 feat: replace calendar date inputs with a wheel-style date picker
Adds DateWheelPicker (src/components/ui/date-wheel-picker.tsx) — an
iOS-style bottom-sheet with scroll-snap day/month/year columns — and
swaps out the 6 remaining <input type="date"> usages in PetForm
(birthday, adoptedAt) and HealthDashboard (weight log, vet visit,
vaccine given/due dates). Closes the last open DEBUG-List.md item.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-16 19:44:50 +02:00
adminandClaude Sonnet 5 f14be58c06 feat: engagement-counter denormalization, pagination fixes, dead-code cleanup
Bundles the 2026-07-12 code-audit session (Clusters A/B/C/D partial/E/F):
denormalized Post/Advertisement reaction/comment/repost counters synced
transactionally instead of live _count queries; real cursor-based pagination
for followers/following/blocks lists; assertPetOwnership + formatRelativeTime
centralized; dead r2.ts + AWS SDK deps removed; missing DB indexes added;
account-deletion flow, mention notifications, pull-to-refresh feed, and
mobile UI/i18n fixes from the surrounding sessions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-16 19:22:23 +02:00
admin c583970404 fix: Kommentarbereich dynamisch — schrumpft bei wenig Kommentaren, scrollt ab 30dvh 2026-06-27 10:18:47 +02:00
admin 4556ef4e97 fix: mention-dropdown Umrandung passt sich Zweizeiligkeit an; neuer Bug Chat-Eingabe-Fix 2026-06-27 10:12:02 +02:00
admin 31602b08eb feat: Sprint 2 — i18n Search+Messages, Mention-Dropdown, Story-Tray, Explore Grid, Legal-Links
- Search-Seite vollständig übersetzt (Search-Namespace in EN+DE)
- Messages-Listenseite übersetzt (pageTitle, noMessages, you, relativeTime etc.)
- @-Mention Dropdown: Avatar 6→9, zwei-zeilig Name+Tierart, orange Fallback
- StoryTray: -mx-6 entfernt, Add-Story-Button jetzt korrekt ausgerichtet
- ExploreCard: lange Rassenamen umbrechen statt aus Grid herausdrängen
- Impressum/Datenschutz/AGB-Links in Profil-Seite (/pets) ergänzt
2026-06-27 08:07:04 +02:00
admin 68503e8915 fix(mobile+comments): #4 correct query invalidation key, #6 add viewport-fit=cover 2026-06-27 07:47:41 +02:00
admin 8deede7235 fix: add parens around nullish coalescing in PostDetailDialog (build error) 2026-06-27 07:36:42 +02:00
admin 58e2840fae fix(mobile+comments): Sprint 1 — nav visibility, messages button, optimistic comments
- MobileNav: fixed mail icon top-right corner (always reachable on mobile)
- MobileNav: iOS safe-area-inset-bottom padding so nav is never hidden
- MobileNav: larger icons (h-6) + better contrast on inactive tabs (text-foreground/60)
- PostDetailDialog: optimistic comment update — comment appears immediately on submit
  with pending indicator; rolls back input on error
- i18n: added Comments.sending key (EN/DE)
- PostCard: minor style cleanup (pre-existing session changes)
- Explore: pre-existing session changes
2026-06-27 07:27:47 +02:00
admin dc4c196721 feat: i18n translations, PostDetailDialog 2-col layout, feeding info health module
- Translate ProfileTabs, ExploreCard, PostDetailDialog, HealthDashboard (EN + DE)
- PostDetailDialog: Instagram-style two-column layout (image left, comments right)
- Fix sm:max-w-[960px] override for dialog width on all breakpoints
- ActivePetInitializer: auto-set first pet on login if activePetId is null
- Onboarding: set activePetId immediately after first pet creation
- Add PetFeedingInfo model (foodType, feedingTimes, dailyAmountG, foodBrand, specialDiet)
- FeedingSection component with view/edit/delete, shown in HealthDashboard
- Health Card public page includes feeding info block
- Delete CommentSheet.tsx (dead code)
2026-06-23 14:39:21 +02:00
admin 2568c32cf4 feat: notification polling 30s->10s, video thumbnails, @-mentions
- NotificationBell: refetchInterval 30000->10000, refetchOnWindowFocus, i18n label
- ProfileTabs: Mux thumbnail for VIDEO posts in grid, play icon overlay
- parse-caption: add mention segment type for @word parsing
- MentionTextarea: new component with @-autocomplete via search.pets
- PostCard: render @mentions as blue links to /search
- CommentSheet: parse comment bodies for mentions, use MentionTextarea
- PhotoPostForm: use MentionTextarea for caption input
2026-06-23 06:17:58 +02:00
adminandClaude Sonnet 4.6 6542c06e02 fix: auto-set first pet on login, resize detail view, fix Clerk types
- Sidebar + MobileNav: auto-call setActivePet(pets[0]) when activePetId
  is null so Follow/reaction buttons work immediately after onboarding
- PostCard detail mode: reduce image max-h from 70dvh to 50dvh so the
  footer (paw + comment buttons) stays visible within the dialog
- ProfileTabs dialog: reduce max-h from 90dvh to 85dvh
- Auth pages: remove layout.logoPlacement and colorText (not in Clerk
  Variables type, caused Docker build failure)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 05:58:57 +02:00
adminandClaude Sonnet 4.6 8429e88891 fix(i18n): use useTranslations in EmptyFeed (Client Component fix)
EmptyFeed is rendered inside FeedList ("use client"), so getTranslations
(server-only) caused a white screen. Switched to useTranslations hook.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 20:42:15 +02:00
adminandClaude Sonnet 4.6 376ea74b36 feat(i18n): translate FeedList, PostCard, CommentSheet, FollowButton, MessageThread
Adds PostCard, Comments, Follow, Messages, FeedList namespaces to en/de
messages. Moves formatRelativeTime/formatDay into components so they can
use the t() hook. All hardcoded strings replaced with next-intl calls.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 14:18:18 +02:00
admin 4b17537a9a feat(i18n): add EN/DE language support with next-intl
- Install next-intl@4 with cookie-based locale switching (NEXT_LOCALE)
- Add LanguageSwitcher component in Sidebar footer
- Translate Nav, PostTypeSheet, Feed, Explore, WelcomeCard, PetForm,
  AvatarUpload, and Onboarding pages (EN + DE)
- Add SpeciesTranslation and BreedTranslation DB models for locale-aware
  taxonomy; listSpecies and listBreeds accept optional locale param
- Seed German translations for all 3 species and 45 breeds
- Add SQL migration script for Supabase (add_i18n_translations.sql)
- fix(invite): secure cookie flag and set-invite route for HTTPS
2026-06-22 14:01:16 +02:00
admin 195df0154d feat(legal): redesign Impressum and Datenschutz with PawFeed branding 2026-06-21 21:46:30 +02:00
admin ac19d0fd2c fix(explore): link avatar and name to pet profile page 2026-06-21 21:11:31 +02:00
admin e5264e3ca3 feat(legal): add Nutzungsbedingungen page and link in sidebar footer 2026-06-21 20:58:10 +02:00
admin fad016973a fix(sidebar): pin to viewport height so footer stays visible 2026-06-21 18:15:26 +02:00
admin 5800e06cd9 feat: ad engagement, invite gate, sidebar footer, docker setup
Ad interactions (reactions/comments/reposts on ads):
- Schema: AdReaction, AdComment, AdRepost models with RLS enabled
- tRPC: new ads router (getFeedData, toggleReaction, addComment, listComments, toggleRepost)
- AdCard: fully interactive card with paw, comment sheet, repost — mirrors PostCard style
- getActiveAds includes _count for advertiser engagement metrics

Invite system enforcement:
- proxy.ts: /sign-up blocked without pf_invite cookie
- INVITE_REQUIRED=false env var disables gate for post-beta live launch
- Full invite flow: /join → cookie → /sign-up → consume-invite → 3 codes issued to new user
- Admin: listInviteCodes, createRootInvite, revokeInvite procedures

Sidebar mini-footer:
- Impressum + Datenschutz links always visible at sidebar bottom
- PawFeed Alpha-Test copyright line

Docker self-hosting:
- docker/Dockerfile: 3-stage build using Next.js standalone output
- docker/docker-compose.yml: env_file + build-args for NEXT_PUBLIC_ vars
- .dockerignore at project root
- next.config.ts: output standalone for minimal image
- .env.example: documented INVITE_REQUIRED
2026-06-21 18:12:13 +02:00
admin a9793a5ad2 fix(ads): AdCard matches PostCard layout — same header, image, footer structure 2026-06-21 16:23:24 +02:00
admin 6f671cfc63 fix(ads): render image in AdCard feed, add image upload to ad dialog 2026-06-21 16:21:38 +02:00
admin b5f02fe7a4 feat(admin): add hidden admin panel, ban system, ads, and legal pages
- Admin panel at /p/[ADMIN_SECRET]/* — middleware returns 404 for wrong/missing secret
- tRPC adminRouter: getStats, listPosts, deletePost, listComments, deleteComment,
  listUsers, banUser/unbanUser, listAds, createAd/updateAd/deleteAd, getActiveAds,
  listModerators, grantRole/revokeRole, getModerationLog, getActiveBan
- Prisma schema: AdminRole, UserBan, Advertisement, ModerationLog models added
- Ban enforcement: (app)/layout.tsx checks active ban on every request → /banned
- Feed ad injection: active ads shown every 7th post in FeedList (AdCard component)
- Clerk middleware.ts: protects all routes; /p/[secret] validated before auth check
- Impressum + Datenschutz pages: contact data base64-obfuscated, decoded client-side only
- shadcn Switch component added
- ADMIN_SECRET + ADMIN_OWNER_ID documented in .env.example
2026-06-21 15:21:07 +02:00
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 c9f385a9fa fix(profile): replace bottom sheet with centered dialog for post detail
- Sheet side=bottom was anchored to screen bottom edge
- Default SheetContent X button overlapped PostCard 3-dot menu
- Dialog renders centered, showCloseButton=false removes the X
  (backdrop click / Escape still closes; 3-dots remain the only top-right control)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 17:23:21 +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