Phase 2 Interactive Likes Design

Goal

Enable interactive likes (like/unlike) for authenticated users and an in-context login modal for unauthenticated users, scoped to a site (not individual pages), while keeping the existing Like row placement.

Architecture

Likes are stored in a separate Like table keyed by siteId + userId with a unique constraint. A public TRPC query returns { count, likedByViewer } for a site. A protected TRPC mutation toggles the like row and returns the updated count/state. UI optimistically updates and rolls back on errors. Unauthenticated clicks open a modal containing the existing login page content; after session is established, the client completes the pending like action.

Components and data flow

  • LikeRow becomes interactive (client component or client wrapper). It receives siteId from siteMetadata and calls TRPC.
  • TRPC like.getStatus({ siteId }) is public and returns the total count and whether the current viewer has liked.
  • TRPC like.toggle({ siteId }) is protected. It creates or deletes the like row and returns the updated count/state.
  • For unauthenticated users, clicking Like opens a modal that reuses the /login page content (extracted to a shared component). After login, the Like action is retried automatically and the modal closes.
  • Unique constraint conflicts are treated as success or reconciled by re-reading status.

Error handling

  • Mutation errors show a toast and revert optimistic UI changes.
  • Unauthenticated users are guided to GitHub login via the modal without leaving the page.

Testing strategy

  • Unit tests for LikeRow: render states, unauthenticated click opens modal, authenticated toggle updates UI.
  • TRPC router tests for toggle semantics and count correctness.
  • E2E tests optional later due to auth env dependencies.