Commit Graph
78 Commits
Author SHA1 Message Date
admin f899ed5f7c feat(landing): translate landing page copy (Landing i18n namespace)
The landing page was hardcoded English, so switching languages had no
visible effect on it — added a "Landing" namespace to both message
catalogs and wired src/app/page.tsx up via getTranslations, matching
the pattern already used by other server-rendered pages.
2026-08-11 20:09:13 +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 07a1cb7768 feat(landing): add a public landing page at / (Gitea #4 follow-up)
/ unconditionally redirected to /feed, gated entirely by proxy.ts's
auth.protect() — so logged-out visitors AND social-media crawlers
scraping og:image for link previews got bounced to Clerk's hosted
sign-in before Next.js ever rendered this route's metadata (surfaced
as Facebook Sharing Debugger's "og:image should be explicit" warning,
since the crawler never saw our actual tags).

Adds "/" to proxy.ts's public matcher and moves the
authenticated-redirect into the page itself via a plain auth() read
(not .protect()), so it can render a real hero page — matching the OG
image's warm gradient + paw-mark visual language — for logged-out
visitors and crawlers alike, while still bouncing authenticated users
straight to /feed as before.
2026-08-11 19:25:52 +02:00
admin 68f0c8be5e fix(seo): swap in the corrected OG image (2 fixes from designer review) 2026-08-11 19:12:55 +02:00
admin 2e5bacb966 feat(seo): swap the generated OG image for the designer-refined one (Gitea #4)
Replaces the code-generated opengraph-image.tsx (placeholder circles,
default font) with a static opengraph-image.png exported from the
designer's refined SVG — a properly-shaped paw mark and a different
typeface. The source SVG traces letterforms as vector paths and embeds
a raster background, which isn't practical to reproduce in a
next/og ImageResponse component, so it's shipped as a static asset
via Next.js's file-convention instead (same og:image/twitter:image
wiring, no proxy.ts change needed — same route path as before).
2026-08-11 19:12:11 +02:00
admin e47eed3a9d feat(seo): add a generated 1200x630 OG image (Gitea #4)
Social-sharing previews had no image — layout.tsx's OG/Twitter tags
existed but pointed at nothing. Adds src/app/opengraph-image.tsx using
next/og's ImageResponse to render a branded card (paw mark, wordmark,
tagline) at request time instead of shipping a static asset, and
switches twitter.card to summary_large_image now that there's an
image for it to show.
2026-08-11 15:27:43 +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 f638239711 fix(admin): clip portrait thumbnails to the square grid cell (Gitea #7)
The aspect-square container had no overflow-hidden, so with overflow
default-visible its own box height wasn't enforced against a
percentage-sized child image — portrait photos fell back to their
intrinsic (taller) aspect ratio instead of getting cropped square,
found while visually verifying the deployed grid. Landscape images
were unaffected, which masked the bug in earlier local testing.
2026-08-11 14:52:20 +02:00
admin ed6842bc39 feat(admin): redesign Posts panel as a 3-column media grid (Gitea #7)
The list view made post content hard to scan — thumbnails rendered at
inconsistent sizes and captions were easy to miss. Switches to a
responsive grid (3 cols on desktop) with a fixed-size thumbnail per
card, a same-size placeholder for posts without media, a caption
below each card, and a play-icon overlay + Mux thumbnail for video
posts (listPosts now also selects videoPost.muxPlaybackId).
2026-08-11 14:47:06 +02:00
admin 5e2a11e44f feat(admin): route GDPR export downloads through pawfeed.org instead of Supabase (Gitea #6 follow-up)
Replaces the emailed Supabase signed URL with a bespoke DataExportToken
DB record, delivered via a new /api/gdpr-export/[token] route that
streams the file server-side. The storage backend is now an internal
implementation detail; every link the user sees reads pawfeed.org.
2026-08-11 14:21:39 +02:00
admin f12dec05bd feat(admin): add GDPR Art. 15 data-export feature (Gitea #6, Plan B)
Aggregates everything held about an Owner (gdpr-export.ts: pets, posts,
stories, comments, reactions, reposts, follows/blocks, health records,
messages, notifications, reports, ban/consent history, Clerk profile)
into a single JSON bundle, uploads it to a private Supabase Storage
bucket (data-export-storage.ts — separate from the public pet-avatars
bucket used for media), and emails the owner a 7-day signed download
link via the new SMTP module. Triggered from a new export button per
user in /p/[secret]/users, logged to ModerationLog.

Deliberately not a zip with embedded media bytes: avoids new archiving
dependencies, the risk of a slow synchronous request for owners with
many posts, and the fact that Mux video assets aren't trivially
re-downloadable — media is referenced by its existing CDN URL instead.
Ad interactions and issued invite codes are not yet included, flagged
in the export's own scope note rather than silently omitted.

Verified end-to-end against the live shared DB before committing:
built a real export, uploaded it, generated a signed URL, and sent a
real test email — all succeeded.
2026-08-11 11:53:27 +02:00
admin 328e54c68e feat(admin): show Clerk profile data and add a Team-messaging channel to the users panel
Gitea #6 (parts 1+2): admin.listUsers now batch-fetches email/name from
Clerk (users only stored the Clerk userId locally, everything else
lives in Clerk) instead of showing a bare userId. Adds
admin.startTeamConversation, which lazily creates a "PawFeed Team"
system Pet under ADMIN_OWNER_ID and reuses the existing pet-to-pet
Conversation/Message model instead of a parallel admin-inbox system —
the admin panel switches ActivePet to Team and opens the ordinary
/messages/[conversationId] thread, so no new UI is needed on the
recipient's side.

Known limitation: only the literal ADMIN_OWNER_ID account can act as
Team, since assertPetOwnership requires an exact owner match.
Extending this to every AdminRole holder is left for a follow-up.

Part 3 (GDPR Art. 15 data export) is deliberately not included —
research only per the issue's own "erst prüfen dann bauen" note.
2026-08-11 11:06:42 +02:00
admin cc82f8102a fix(onboarding): move LanguageSwitcher out of absolute positioning to avoid mobile overlap
Absolutely positioning it at top-4 right-4 over the centered content
overlapped the step headings on narrow viewports (reported live).
Gives it its own row in a flex-col layout instead, so it never
competes for space with the card content below it.
2026-08-11 10:32:12 +02:00
admin 4d9727f0d1 fix(i18n): fall back to Accept-Language and add a language switcher to onboarding
New visitors always got the English default because locale resolution
only ever read the NEXT_LOCALE cookie — never set until a user visits
a page with the LanguageSwitcher, which only lives in the authenticated
app Sidebar. /onboarding/pet runs before that, so a German browser
landed in English with no way to change it (reported live after
deploying the upload-consent step from Gitea #3).

Adds an Accept-Language fallback in src/i18n/request.ts (only consulted
when no cookie is set) and renders the existing LanguageSwitcher on the
onboarding page so users can override the guess.
2026-08-11 10:27:58 +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 5473978763 docs(legal): remove alpha-phase content from Nutzungsbedingungen, harden against Abmahnung
Gitea #2: the site is now public (INVITE_REQUIRED=false), so the
alpha-status clause and invite-code registration requirement no longer
apply and are removed. Hardens the remaining terms: age/capacity
requirement, DSA host-provider privilege with a notice-and-action
section referencing the existing report feature, a proportionate
suspension clause, a limited-liability clause based on the
unentgeltliche-Nutzung privilege (§§ 521, 599, 690 BGB), a
§308-Nr.5-BGB-compliant change clause with objection period, an EU
consumer-protection carve-out, and a severability clause.
2026-08-11 09:33:45 +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 46b403f30f feat(observability): add global-error.tsx for Sentry App Router coverage
Sentry's Next.js App Router setup requires this file to capture React
rendering errors at the root layout level — GlitchTip's own setup
checklist flagged it as missing.
2026-08-10 13:33:38 +02:00
admin 2bf7832341 feat(seo): add metadataBase and OpenGraph/Twitter metadata
Public launch prep — link previews on social/messaging apps were
falling back to bare URLs with no title/description. No branded OG
image exists yet (public/ only has create-next-app placeholders), so
this ships text-only; a real 1200x630 image is a follow-up.
2026-08-10 12:47:17 +02:00
admin f43541a4cc feat(security): add Redis-backed rate limiting and CSP/security headers
Public go-live hardening: Redis-backed rate limiting on the previously
invite-gated public surface (explore browsing, post/comment creation,
invite-cookie routes) plus CSP, HSTS, and standard security headers on
every response, now that the invite gate no longer filters traffic.
2026-08-10 07:57:56 +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 25fa2a3bec feat(i18n): resolve Species/Breed display names via SpeciesTranslation/BreedTranslation
User found "Cat" showing English on a German-locale profile page. Root
cause: Species.name/Breed.name are the canonical English values; a
SpeciesTranslation/BreedTranslation table + seed data (Hund/Katze/Vogel
etc.) already existed in the schema, and PetForm's species/breed picker
already used it correctly via an explicit locale input param — but every
*display* surface (pet profile, PetCard, Sidebar/MobileNav, search,
explore, followers/following lists, mention @-search, health-card) just
read the raw untranslated name.

Adds `locale` to the tRPC context (createTRPCContext reads the
NEXT_LOCALE cookie the same way i18n/request.ts does), so read
procedures can resolve translations without every caller needing to
pass a locale param. New src/lib/localized-name.ts::pickLocalizedName()
does the lookup with a same-shape fallback to the raw name.

Updated: pets.ts (list/byId/getProfile/update), search.ts (pets/byOwner),
explore.ts (listPets), follows.ts (listFollowers/listFollowing).
health-card/[token]/page.tsx (public, unauthenticated, intentionally
all-German) resolves with a fixed "de" locale instead of ctx.locale,
consistent with its existing hardcoded WeightChart locale.

Verified the seed data is actually present in the production DB
(SpeciesTranslation has Dog→Hund, Cat→Katze, Bird→Vogel) — this fix is
immediately effective on deploy, not blocked on a missing seed run.
2026-07-23 15:19:55 +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
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 d7234f55bd feat(legal): expand Nutzungsbedingungen prohibited-content section
Section 5 now explicitly names pornographic/sexually explicit content
(with a zero-tolerance note on depictions of minors) and a broader
catch-all for other content illegal under German law (violence
glorification, Volksverhetzung, unconstitutional symbols, terrorism,
illegal weapons/drug trade) instead of the vague "illegal content of
any kind" line. Bumps the "Stand" date to reflect the content change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-16 19:51:18 +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 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 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
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 f7183f93c7 fix(clerk): remove colorText from appearance variables (not in type)
colorText is not part of Clerk Variables type in current version,
causing TypeScript build failure in Docker.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 20:48:52 +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 8645421c1e feat(legal): apply LegalLayout to Nutzungsbedingungen page 2026-06-22 05:48:08 +02:00
admin 195df0154d feat(legal): redesign Impressum and Datenschutz with PawFeed branding 2026-06-21 21:46:30 +02:00
admin e5264e3ca3 feat(legal): add Nutzungsbedingungen page and link in sidebar footer 2026-06-21 20:58:10 +02:00
admin b38c9b046e fix(invite): add secure flag to pf_invite cookie for HTTPS production 2026-06-21 20:47:19 +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 6f671cfc63 fix(ads): render image in AdCard feed, add image upload to ad dialog 2026-06-21 16:21:38 +02:00
admin 0f34a2c9c6 feat(admin): show post preview in reports — images, caption, pet, counts 2026-06-21 16:10:01 +02:00
admin e2d14654c8 feat(admin): reports page with dismiss and delete-post actions 2026-06-21 16:00:33 +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
admin e407064866 feat(explore): add trending tab with hot posts and hashtags
- explore.getTrending: top-12 photo posts by reaction count (48h window)
  and top-10 hashtags by post count (7d window)
- Explore page opens on Trending tab by default; species/breed tabs
  preserved with breed filter hidden when not applicable
- Trending UI: hashtag chips linking to /hashtag/[tag] + 3-col post
  grid with paw-count overlay and PostCard detail dialog

feat(follows): auto-trigger follower milestone posts

- After each follow, count followers for followeePetId and check against
  all thresholds (500 / 1K / 2.5K / 5K / 10K / 25K / 50K / 100K /
  250K / 500K / 1M)
- Guard against duplicate awards via existing Milestone lookup
- Fire-and-forget: milestone post created + fanned out to follower feeds
  without blocking the follow response
2026-06-21 14:41:20 +02:00
adminandClaude Sonnet 4.6 23c570b2af fix(health): enforce privacy — remove emergency vet from public health card
Emergency vet contact data (phone, address, map) was visible on the
token-shared health card URL. Health data is owner-private; the health
card is the single sharing exception but should not include internal
emergency contact details.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 17:52:58 +02:00