Skip to content

Modern UI: React 19 + shadcn rewrite with messenger features (typing, unread, read receipts)#6

Open
iwanmitowski wants to merge 9 commits into
feature/microservicesfrom
feature/ui-modernization
Open

Modern UI: React 19 + shadcn rewrite with messenger features (typing, unread, read receipts)#6
iwanmitowski wants to merge 9 commits into
feature/microservicesfrom
feature/ui-modernization

Conversation

@iwanmitowski

Copy link
Copy Markdown
Owner

Modernizes the SPA on the current 2026 stack and adds full messenger depth (typing indicators, unread badges, read receipts) on top of the microservices backend from #5. Stacked on feature/microservices — merge #5 first.

Frontend rewrite (web/, replaces the CRA Web-FrontEnd/)

Stack: Vite + React 19 + TypeScript · Tailwind CSS v4 (CSS-first, oklch tokens) + shadcn/ui incl. the June-2026 chat primitives · motion v12 · sonner · TanStack Query v5 (+ react-virtual) · react-router 7 · react-hook-form + zod · Zustand. Served by nginx from a multi-stage Docker build (dev-server container retired).

  • Fresh dark-first design with a rose→amber brand gradient, light-mode toggle, desktop rail + mobile bottom tabs (safe-area aware), proper branding/favicon.
  • Discover: motion-driven swipe deck (drag rotation, LIKE/NOPE stamps, velocity fling, stacked peek), tap-through photo carousel, live preferences sheet, animated match overlay.
  • Messages — messenger split-pane replacing the old chat modal: conversation list with last-message previews, unread badges and live typing flashes; thread with day separators, sender grouping, optimistic send (swapped for the hub echo by id), paste/attach image upload-then-send, typing dots, Seen ticks, presence panel + join/leave markers for public rooms; mobile stacked navigation.
  • In-app notifications: one app-level SignalR connection feeding the TanStack Query cache; custom "It's a match!" toast (the MatchReceived push finally has a consumer) and new-message toasts that navigate; live nav unread badge; reconnect indicator.
  • Auth: 3-step register wizard (zod per step), verify + reset flows. Profile: photo manager with profile-pic invariant. Admin: stat cards + users data table with URL-synced tabs and ban/unban dialog. Landing for logged-out visitors.
  • Every legacy API contract preserved (multipart field names, hub payloads, localStorage keys, silent-refresh interceptor).

Backend additions (B1)

  • Realtime hub: Typing (transient), MarkRead (Kafka-durable + live ReadReceipt), server message Id now included in ReceiveMessage, and a direct MessageNotification push to the 1:1 peer so unread badges work without joining the room group.
  • Chat service: room_reads watermark table + chat.room-read consumer (forward-only, idempotent), POST api/chat/summaries (last message, unread count, peer read state per authorized room), message Id exposed in history.
  • New chat.room-read topic (kafka-init + contracts). 4 new tests → 41 total.

Verification

  • dotnet test LoveNet.sln — 41/41 green.
  • scripts/smoke-test.ps1 — extended to 25 steps (typing signal, live read receipt, Kafka-persisted watermark via summaries) — PASSED against the full compose stack with the new nginx frontend.
  • npm run build clean (tsc strict + vite).

🤖 Generated with Claude Code

ivanmitovski and others added 9 commits July 13, 2026 15:43
- Contracts: RoomReadEvent + chat.room-read topic (kafka-init line; keyed by
  roomId like the other chat topics)
- Realtime hub: Typing() transient broadcast to OthersInGroup; MarkRead()
  publishes RoomReadEvent (durable) then broadcasts ReadReceipt (live);
  ReceiveMessage now carries the server-assigned message Id; 1:1 messages
  additionally push MessageNotification to the peer via Clients.User so
  unread badges/toasts work without being joined to the room group
- Chat: room_reads watermark table (RoomId,UserId -> LastReadAt; deliberately
  not per-message rows because messages has a composite partitioned PK),
  RoomReadConsumer (forward-only upsert, naturally idempotent),
  POST api/chat/summaries (last message + unreadCount + peerLastReadAt per
  authorized room; peer resolved from the provisioned Room row), message Id
  added to the history projection
- Tests: 4 new (summary math, no-watermark case, authorization omission +
  public-room null peer read, consumer watermark never regresses) -> 41 total
- Smoke test extended: second hub connection as the peer verifies typing,
  live ReadReceipt, and the Kafka-persisted read state via summaries.
  Full 24-step run PASSED against the compose stack.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dcn/ui)

web/ replaces the CRA app (old tree removed at ship time):
- Tailwind v4 CSS-first theme: dark-first oklch tokens, rose->amber brand
  gradient, Inter, safe-area/gradient utilities; shadcn components incl. the
  June-2026 chat primitives (message-scroller/message/bubble/attachment/marker)
- typed API layer mirroring every service contract (multipart field names
  Image/NewImages/Images-as-JSON/file preserved), axios with the silent
  401->refreshToken replay; auth store keeps the legacy localStorage auth
  key the interceptor + hub token factory read
- app-level SignalR core: one connection per session, events fan into the
  TanStack Query cache (ReceiveMessage dedupes optimistic bubbles by id,
  ReadReceipt patches summaries) and sonner (custom match + message toasts
  that navigate); transient signals (typing/presence) via a tiny typed emitter
- app shell: desktop rail + mobile bottom tabs with live unread badge,
  theme toggle, guards, router (exported instance so toasts can navigate),
  shared useConversations hook (matches x summaries join)
- login page (RHF + zod); stub pages for the parallel feature builds
- npm run build green (tsc + vite)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3-step register wizard (account -> about you -> photos & terms) with per-step
zod validation, direction-aware motion transitions, geo selects, photo picker
with profile-pic star and object-URL hygiene, condensed T&C dialog; verify
page (token + resend modes); dual-mode reset page. Same multipart contract
as the old app via the typed API client.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
motion v12 fling deck (drag rotation, LIKE/NOPE stamps, velocity/offset
thresholds, stacked peek that promotes without remounting), tap-through photo
carousel, distance chip, action bar sharing the drag code path, preferences
sheet writing live to the persisted store, match overlay with Send-a-message
CTA, admin redirect, skeleton/empty states.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Conversation list (matches x summaries: previews, unread badges, live
typing flash, communities section) + thread on the shadcn chat primitives:
anchored scroller with preserved prepend position, day markers, 5-minute
sender grouping, optimistic send swapped for the hub echo, paste/attach
image upload-then-send, throttled typing signals, markRead on view with
live seen ticks (summaries cache + ReadReceipt events), join/leave markers
and presence panel for public rooms, reconnect re-join. Mobile stacked views.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Profile: photo manager (star/trash/add with single profile-pic invariant) +
details form, cache-patching save. Admin: stat-card dashboard linking into a
users data table with debounced search, All/Banned tabs synced to the URL,
infinite pages, ban/unban dialog patching cached rows. Landing: gradient hero,
feature cards, advice strip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- web/ multi-stage Dockerfile (node build -> nginx static, SPA fallback,
  immutable asset caching); compose frontend service now serves nginx on 3000
- smoke-test hub probe moved to a disposable node container on the compose
  network (the frontend container no longer ships node); full 25-step run
  PASSED against the new stack
- Web-FrontEnd (CRA) deleted; README stack/tests refreshed (41 tests,
  regenerated capture)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Like/Pass action bar drove the top card through one shared ref, but with
AnimatePresence the outgoing card unmounts ~120ms AFTER its successor mounts,
so the old card's imperative-handle cleanup nulled the ref the new top card
had just populated - every button click after the first swipe hit null and
silently did nothing.

Replaced with ownership-checked registration: the top card registers a swipe
handle on mount and its cleanup only clears the slot if it still owns it.
Verified with a Playwright probe against the built container: consecutive
Like/Pass clicks both advance the deck; 61fps idle and mid-drag at 4x CPU
throttle, zero leaked animations (playwright added as a devDependency).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
About now credits the refactor as a Claude Fable 5 test; new section walks
every communication path: SPA->gateway (REST + websocket), the single
HTTP fan-out, the Kafka topic table (producer->consumers, keys, outbox/inbox
semantics), Redis backplane/presence, database-per-service, external
integrations, and local JWT validation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants