mirror of
https://github.com/narrrl/holy-flavor-rater.git
synced 2026-11-04 00:47:02 +01:00
146 lines
6.6 KiB
Markdown
146 lines
6.6 KiB
Markdown
# 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` — added `useRecentRatings`.
|
|
- `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_reaction` table (`user_id`, `rating_id`, `kind`, `created_at`;
|
|
unique on `user_id + rating_id + kind`). Created in `db.rs::ensure_schema`
|
|
(idempotent `CREATE TABLE IF NOT EXISTS` + index on `rating_id`).
|
|
- Endpoints: `POST /api/ratings/{id}/react/`, `DELETE /api/ratings/{id}/react/`.
|
|
Allow-listed `kind`, rate-limited (60/min/user), idempotent on the unique index.
|
|
- `RatingOut` carries `reactions` (kind → count) + `my_reactions` (caller's own).
|
|
Threaded through `build_ratings(viewer_id)` via the `OptionalUser` extractor.
|
|
- Frontend: `ReactionBar` on 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` (`REACTION` rate),
|
|
`dto.rs` (`reactions`/`my_reactions`), `service.rs` (`load_reactions`,
|
|
`build_ratings` viewer param), `routes/ratings.rs` (`react`/`unreact`),
|
|
plus call-site updates in `routes/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).
|
|
`top` orders by score desc then recency; category resolves slug → flavor ids.
|
|
- Frontend: sticky filter bar — `Recent | Top rated` sort chips + `All` plus a
|
|
chip per category (reuses `useCategories`). Discover now paginates like
|
|
Following; page resets on tab/sort/category change.
|
|
|
|
#### Files touched
|
|
|
|
- `backend-rs/src/routes/ratings.rs` (`discover` handler + 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. Returns `UserOut` (reuses `build_users`),
|
|
re-sorted to the activity ranking after the id-ordered load.
|
|
- Frontend: `SuggestedFollows` card (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` (`suggested` handler + route).
|
|
- `frontend/src/api/queries/useCommunityFeed.ts` (`useSuggestedUsers`),
|
|
`frontend/src/pages/CommunityFeed.tsx` (`SuggestedFollows` sidebar 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
|
|
of `new_flavor` drops + `milestone` events. No persistent event table — items
|
|
are synthesized on read from `flavor.created_at` and, 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.**
|