Skip to content

Frontend Pages — Thread Listing, Detail, Create & Profile #42

Description

@mrboxs

Summary

All backend APIs are complete (Threads CRUD, Replies CRUD, Votes, Me). Auth + onboarding flow is fully wired (Better Auth with GitHub OAuth, auto-generated temp username for OAuth users, onboarding page for username selection).

The remaining work is frontend pages. The home page (_app/index.tsx) is still the TanStack Start boilerplate. No thread detail, create thread, or user profile pages exist yet.


Status Overview

✅ Done

  • Backend: Threads (CRUD + list + top), Replies (CRUD + nested), Votes, Me
  • Auth: Better Auth + GitHub OAuth + email/password
  • Auth flow: sign-in page → OAuth callback → temp username auto-generated → onboarding page → redirect to home
  • DB schemas: all tables with correct columns, indexes, relations
  • Shared DTOs & constants: all input/output schemas via @trid/shared
  • oRPC client utils: threadORPC, replyORPC, voteORPC via @orpc/tanstack-query
  • Layout: Root, Auth layout, App layout (sidebar + navbar)
  • UI components: button, card, sidebar, input-group, field, spinner, sonner, skeleton, theme provider

❌ Not Started (this issue)

  • Home page (thread listing)
  • Thread detail page
  • Create thread page
  • User profile page

1. Home Page — Thread Listing (_app/index.tsx)

Route: / (existing _app/index.tsx, replace boilerplate content)

API: threadORPC.list.useInfiniteQuery() (or orpc.threads.list)

Input schema: listThreadsInputSchema:

{ feed: "discover" | "popular" | "latest", limit: number, cursor?: string }

Output schema: listThreadsOutputSchema:

{ items: Array<{ id, author, title, slug, status, votesScoresCount, repliesCount, lastActivityAt, createdAt, uservote }>, nextCursor: string | null }

Requirements

  • Feed tabs: 3 tabs — Discover, Popular, Latest
    • Active tab highlighted, click to switch feed
    • URL search param ?feed=discover for shareable state
    • Default: discover
  • Thread cards: Each card shows:
    • Author avatar + name + username
    • Title (link to thread detail)
    • Vote count with upvote/downvote buttons (if authenticated)
    • Reply count
    • Relative time ("2h ago", "3d ago")
  • Cursor pagination: "Load more" button at bottom
    • Loading skeleton while fetching
    • "No more threads" when nextCursor is null
  • Empty state: "No threads yet" with CTA to create first thread
  • Error state: Retry button on fetch failure
  • Vote interaction: Click upvote/downvote → voteORPC.thread.useMutation() → optimistically update UI

Key oRPC hooks

const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
  threadORPC.list.useInfiniteQuery(
    { feed: selectedFeed, limit: 20 },
    { getNextPageParam: (lastPage) => lastPage.nextCursor },
  )

const voteMutation = voteORPC.thread.useMutation()

Thread card component

Props: thread: ThreadListItem

  • Link to /$slug for title
  • Author row (avatar + name + username + time)
  • Vote button group (upvote/downvote count)
  • Reply count icon

2. Thread Detail Page

Route: /_app/threads/$slug (new file: apps/web/src/routes/_app/threads/$slug.tsx)

API:

  • orpc.threads.get({ slug })threadDetailOutputSchema
  • orpc.replies.list({ threadSlug, sort, limit, cursor })listRepliesOutputSchema

Requirements

  • Thread content: Full thread view with title, author info, content body, timestamps
  • Vote buttons: Upvote/downvote on thread + each reply
  • Reply count + sort dropdown: Sort by Top / Latest / Oldest (?sort=top)
  • Reply list:
    • Each reply shows author, content, vote count, timestamp
    • Nested replies (depth-based indentation)
    • "Load more" pagination
    • Reply form at bottom (authenticated only)
  • Create reply form: Textarea + submit button
  • Thread actions: Edit/delete buttons if current user is author
  • 404 handling: "Thread not found" when slug doesn't exist
  • Loading state: Skeleton loader
  • Delete confirmation: Dialog/modal before soft-delete

Key oRPC hooks

const { data: thread } = orpc.threads.get.useQuery({ slug })

const { data: replies, fetchNextPage } =
  replyORPC.list.useInfiniteQuery(
    { threadSlug: slug, sort: selectedSort, limit: 20 },
    { getNextPageParam: (lastPage) => lastPage.nextCursor },
  )

const createReplyMutation = replyORPC.create.useMutation()
const voteMutation = voteORPC.reply.useMutation()

3. Create Thread Page

Route: /_app/threads/new (new file: apps/web/src/routes/_app/threads/new.tsx)

Protected: Redirect to sign-in if not authenticated

API: orpc.threads.create({ title, content })

Requirements

  • Form: Title input + content textarea (markdown or plain text)
  • Validation: Title 1-300 chars, content min 1 char
  • Submit: orpc.threads.create.useMutation() → on success, navigate to /$slug
  • Error handling: Show validation errors inline, API errors as toast
  • Cancel/back button: Navigate back to home
  • Loading state: Disable submit button + spinner while submitting

4. User Profile Page

Route: /_app/users/$username (new file: apps/web/src/routes/_app/users/$username.tsx)

API:

  • orpc.me for current user's own profile
  • orpc.threads.list({ feed: "latest", author: username }) for user's threads

Requirements

  • Profile header: Avatar, name, username, bio, pronouns, location, join date
  • Banner image if set
  • User's threads tab: List of threads by this user
  • Edit profile button (only if current user's own profile)
    • Navigate to settings page
  • Loading & error states
  • 404 handling for non-existent username

5. Polish & Cross-cutting

  • Mobile responsive: All pages should work on mobile (sidebar drawer, full-width content)
  • Relative time: Consistent time display across all pages (use @trid/shared/utils if exists, or a simple helper)
  • Optimistic updates for votes to feel instant
  • Error boundaries for each route
  • Empty states for lists with zero items
  • Keyboard accessibility for vote buttons, reply forms, navigation

Implementation Order

  1. Home page — Thread Listing (highest priority, landing page)
  2. Thread Detail Page (core user flow)
  3. Create Thread Page (content creation)
  4. User Profile Page (identity & history)
  5. Polish & cross-cutting fixes

Each page is independently buildable since the backend is fully ready.


Tech Notes

  • Use @orpc/tanstack-query hooks (threadORPC.list.useInfiniteQuery(), etc.) — already configured in apps/web/src/libs/orpc.ts
  • Use TanStack Router file-based routes — create new files under apps/web/src/routes/_app/
  • Use @trid/ui components (Button, Card, Skeleton, etc.)
  • Use authClient from #/libs/auth-client for auth actions
  • Use sonner toast for notifications
  • TanStack Router search params for feed/sort/cursor state in URL
  • Vote mutations should cache-invalidate the relevant thread query after success

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions