6.6 KiB
Community Tab Redesign
The Community tab is the oldest part of the site and doesn't fit the robust, professional design of the rest. This document tracks the redesign.
Starting state
frontend/src/pages/CommunityFeed.tsx — a paginated feed of ratings from
followed users only, with a single sidebar card ("Circle's Top Rated"). Replies
are hidden behind a per-card collapse. Loading is a bare center spinner.
Core problems
- Cold start is dead. No follows → empty feed → no reason to return. The
backend
ratings/feed/is gated on the follow graph and returns nothing for users who follow no one. - Single content type. Only ratings. No new-flavor drops, profile comments, follow events, or milestones.
- No engagement loop. No reactions/likes. Replies buried in a collapse. The feed is a read-only wall.
- No filtering/sort. Can't sort recent/top or filter by category, even though
the payload already carries
category_name/category_slug. - Sparse sidebar. One card wastes the 4-column rail.
Phase 1 — frontend-only (DONE)
No backend changes. Reuses the existing GET /api/ratings/recent/ endpoint
(global, AllowAny, 10 newest ratings with a comment).
- Following | Discover tabs. Discover surfaces the whole community's newest
ratings, so the page is useful even when you follow nobody — fixes the
cold-start hole. Discover is fetched lazily (
enabled: tab === 'discover'). - Skeleton loader (
FeedSkeleton) replaces the center spinner — matches the professional look of the rest of the app. - Inline reply preview. The latest reply shows compact under each card; click expands the full thread + compose box. Replies feel like conversation, not archive.
- Smarter empty states. Empty Following nudges to Discover (button switches tab); empty Discover nudges to explore flavors.
- i18n EN + DE keys added under
community.*.
Files touched
frontend/src/api/queries/useCommunityFeed.ts— addeduseRecentRatings.frontend/src/pages/CommunityFeed.tsx— tabs, skeleton, inline reply preview, empty-state logic.frontend/src/i18n.ts—tabFollowing,tabDiscover,discoverSubtitle,discoverEmpty,discoverEmptyHint,browseDiscover(EN + DE).
Phase 2 — needs backend
1. Reactions (highest engagement lever) — DONE
Lightweight 👍 / emoji on ratings. Social proof, drives return visits.
- New
rating_reactiontable (user_id,rating_id,kind,created_at; unique onuser_id + rating_id + kind). Created indb.rs::ensure_schema(idempotentCREATE TABLE IF NOT EXISTS+ index onrating_id). - Endpoints:
POST /api/ratings/{id}/react/,DELETE /api/ratings/{id}/react/. Allow-listedkind, rate-limited (60/min/user), idempotent on the unique index. RatingOutcarriesreactions(kind → count) +my_reactions(caller's own). Threaded throughbuild_ratings(viewer_id)via theOptionalUserextractor.- Frontend:
ReactionBaron each feed card; optimistic toggle, server-reconciled.
Files touched
backend-rs/src/entities/rating_reaction.rs(new),entities/mod.rs.backend-rs/src/db.rs(table + index),throttle.rs(REACTIONrate),dto.rs(reactions/my_reactions),service.rs(load_reactions,build_ratingsviewer param),routes/ratings.rs(react/unreact), plus call-site updates inroutes/users.rs,routes/admin.rs.frontend/src/components/ReactionBar.tsx(new),frontend/src/pages/CommunityFeed.tsx,api/queries/useCommunityFeed.ts,frontend/src/i18n.ts.
2. Discover feed with sort + pagination — DONE
recent/ is fixed at 10 and unpaginated. Discover is now a real feed.
- New
GET /api/ratings/discover/(AllowAny) accepts?sort=recent|top,?category=<slug>,?page=. Quality bar kept (non-empty comment only).toporders by score desc then recency; category resolves slug → flavor ids. - Frontend: sticky filter bar —
Recent | Top ratedsort chips +Allplus a chip per category (reusesuseCategories). Discover now paginates like Following; page resets on tab/sort/category change.
Files touched
backend-rs/src/routes/ratings.rs(discoverhandler + route).frontend/src/api/queries/useCommunityFeed.ts(useDiscoverFeed),frontend/src/api/keys.ts(communityDiscover),frontend/src/pages/CommunityFeed.tsx(filter bar, discover pagination),frontend/src/i18n.ts(sortRecent,sortTop,allCategories).
3. Who-to-follow — DONE
Sidebar card suggesting active raters the user doesn't follow yet.
GET /api/users/suggested/— up to 5 most-active raters (by rating count), excluding self + already-followed. ReturnsUserOut(reusesbuild_users), re-sorted to the activity ranking after the id-ordered load.- Frontend:
SuggestedFollowscard (avatar + name + follower count + Follow button), optimistic — drops the user from the list on follow, reverts on error.
Files touched
backend-rs/src/routes/users.rs(suggestedhandler + route).frontend/src/api/queries/useCommunityFeed.ts(useSuggestedUsers),frontend/src/pages/CommunityFeed.tsx(SuggestedFollowssidebar card),frontend/src/i18n.ts(whoToFollow,follow,followFailed,followerCount).
4. Richer activity types — DONE
Make the feed alive when friends are quiet.
- New
GET /api/activity/(AllowAny, paginated): a unified, newest-first stream ofnew_flavordrops +milestoneevents. No persistent event table — items are synthesized on read fromflavor.created_atand, for milestones, the timestamp of the rating that crossed a threshold (25/50/100/250/500/1000; one item per flavor at its highest crossed threshold). Candidate set is bounded (latest 40 drops + flavors ≥25 ratings), built once and paged in memory. - Frontend: third Activity tab with its own card style (drop vs. milestone icon), paginated like the others.
Files touched
backend-rs/src/dto.rs(ActivityOut),service.rs(build_activity),routes/activity.rs(new),routes/mod.rs(mount).frontend/src/api/queries/useCommunityFeed.ts(useActivityFeed),frontend/src/api/keys.ts(communityActivity),frontend/src/pages/CommunityFeed.tsx(Activity tab +renderActivityCard),frontend/src/i18n.ts(tabActivity,activitySubtitle,activityEmpty,activityEmptyHint,newDropLabel,milestoneLabel,milestoneText).
Suggested order
Phase 2.1 (reactions) and 2.2 (Discover sort/filter) give the most UX lift per unit of backend work. 2.3 and 2.4 follow. All of Phase 2 is now complete.